Code Craftsman Software craftsmanship and iOS development

Generating the struct's initializer

struct default initializer

Swift has an awesome feature regarding structs and their initializer. By default, an initializer is generated so you can create your struct with the content you want:

public struct Person {
    public let familyName: String
    public let givenName: String
}

let superDupont = Person(familyName: "Dupont", givenName: "Super")

You can do this as long as you are in the same module.
However, if you try to instanciate the struct from outside the module you’ll have this error:

'Person' initializer is inaccessible due to 'internal' protection level

As indicated, it’s because the init is internal, even if you’ve marked your struct as public like above.

Your only solution to access it is to write your own public initializer:

public struct Person {
    public let familyName: String
    public let givenName: String

    public init(familyName: String, givenName: String) {
        self.familyName = familyName
        self.givenName = givenName
    }
}

And now you have one more problem: you need to update this code every time your struct changes.

Wouldn’t it be great if we did not have to write and maintain this code?

Sourcery to the rescue

Sourcery is a code generation tool using SourceKit which has several cool features to use in ths case:

  • source annotations
  • inline insertion of generated code

I have written a template to generate the init method of annotated structs. Here is how this works:

1. Annotate the struct

You add // sourcery: AutoInit just before your declaration:

// sourcery: AutoInit
public struct Person {
    public let familyName: String
    public let givenName: String
}

2. Add AutoInit.stencil to your Sourcery templates

Create a AutoInit.stencil file in your Sourcery templates folder with the following content:

{% for type in types.structs %}
{% if type|annotated:"AutoInit" %}
{% set spacing %}{% if type.parentName %}    {% endif %}{% endset %}
{% map type.storedVariables into parameters using var %}{{ var.name }}: {{ var.typeName }}{% endmap %}
// sourcery:inline:auto:{{ type.name }}.AutoInit
{{spacing}}    {{ type.accessLevel }} init({{ parameters|join }}) { // swiftlint:disable:this line_length
{{spacing}}        {% for variable in type.storedVariables %}
{{spacing}}        self.{{ variable.name }} = {{ variable.name }}
{{spacing}}        {% endfor %}
{{spacing}}    }
// sourcery:end
{% endif %}
{% endfor %}

This template is also available as a gist.

3. Run Sourcery

This will generate the init and insert the code right inside the struct’s declaration:

// sourcery: AutoInit
public struct Person {
    public let familyName: String
    public let givenName: String

// sourcery:inline:auto:Person.AutoInit
    public init(familyName: String, givenName: String) {
        self.familyName = familyName
        self.givenName = givenName
    }
// sourcery:end
}

The template use the auto-inlining feature of Sourcery.
It will take the code between the inline:auto annotations and put it directly inside the specified type and then update the code in place when it needs to change.

4. Enjoy

You can now access this initializer from outside the module and every time you update the structure declaration, it will update itself (when you run Sourcery of course).