Swift Quick Tip - Struct Memberwise Initializers are Internal Only

Swift Quick Tip - Struct Memberwise Initializers are Internal Only
Photo by Maxwell Nelson / Unsplash

A struct is a powerful building block in a Swift developer's tool belt. These simple pass by value Swift objects have 1 powerful and time saving feature over their class counterparts and that is the fact that the Swift compiler automatically generates an init method for you.

struct MyStruct {
   var myStringVar: String
   var myIntVar: Int
}

struct MySecondStruct {
   func myFunc() {
      let myStruct = MyStruct(myStringVar: "myString", myIntVar: 0)
      // do something with myStruct here
   }
}

In the above code, the Swift compiler automatically created the initializer for MyStruct. But what happens when MyStruct is inside of a Swift package and not inside your app's target? Let's find out. The following code is defined inside of a Swift package that is imported into your app via SPM.

public struct MyStruct {
   public var myStringVar: String
   public var myIntVar: Int
}

As you can see, this version of MyStruct has marked itself and all of its properties as public so that they are visible so code outside of the package. So, what happens when we try to initialize this struct from our app? The compile throws this error at you, 'MyStruct' initializer is inaccessible due to 'internal' protection level.

MyStruct initializer is inaccessible due to 'internal' protection level

Well, isn't this super annoying?! I love Swift and its compiler is amazing. But why does it have to do this? Anyway, the solution is easy enough. Your first option is to only initialize the struct internally inside of your package. This will allow you to take advantage of the free init provided by the compiler. If this isn't an option for you then you'll have to implement the init method yourself.

public struct MyStruct {
   public var myStringVar: String
   public var myIntVar: Int
   
   public init(myStringVar: String, myIntVar: Int) {
      self.myStringVar = myStringVar
      self.myIntVar = myIntVar
}

This is a small and easy example, but what if you have a lot of properties inside your struct? Simple, let Xcode generate the init code for you. To do this, "right click" on your struct's name to bring up a menu of options. Select Refactor and then Generate Memberwise Initializer.

Generate Memberwise Initializer menu

This will generate the following code,

public struct MyStruct {
    internal init(myStringVar: String, myIntVar: Int) {
        self.myStringVar = myStringVar
        self.myIntVar = myIntVar
    }
    
    public var myStringVar: String
    public var myIntVar: Int
}

First thing you will need to do is change the Access control level. To do this delete the internal access level and change it to public. Now the compiler warning will be gone and you can move on.

If you want to keep the "free" init provided by the compiler while also exposing a public initializer you can accomplish this by throwing the public init inside of an extension of your struct.

public struct MyStruct {    
    public var myStringVar: String
    public var myIntVar: Int
}

extension MyStruct {
   public init(myStringVar: String, myIntVar: Int) {
        self.myStringVar = myStringVar
        self.myIntVar = myIntVar
    }
}