Generating the struct's initializer
2017-09-24struct
default initializer
Swift has an awesome feature regarding struct
s 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 struct
s.
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).