Writing Class Initializers
So, I have mentioned a few times about initializers on this blog. I want to talk today about writing and using initializers for classes, later we can talk a bit more about how they are different for value types like structs or enumerations.
Initializers do exactly what is sounds like they do, they initialize your instances and get them ready to use. Specifically, before an object can be used, it must set all of its stored properties to valid values. This can be handled in one of three ways:
- Give them default values in their property definition
- Set them during the initializer
- Declare them as optionals
Now, I would not actually recommend declaring them as optionals unless you really need Swift’s optional functionality. In the case of declaring them as optionals, they do not need to be given an explicit default value, nor must they be set in the initializer to be considered valid. Optionals have an implicit default value of nil (well, actually its their .None case since they are actually just enumerations). If you wish to learn more about Swift optionals, check out my previous posts Swift Optionals – Declaration, Unwrapping, and Binding and Optional Chaining and Implicitly Unwrapped Optionals in Swift.
So, the two main ways to set up stored properties are via default values, or via setting them in the initializer, as shown below:
class SampleClass { var aStringWithDefaultValue = "Default Value!" var aStringSetInAnInitializer: String init() { aStringSetInAnInitializer = "From the initializer!" } }
Above is the simplest form of the initializer. It takes no arguments, and then just sets the value internally to that string literal.
In general though, if you are giving it a default value that does not need to be provided, like this string literal, you really should set it as a default value and not in the initializer. For one, it lets you use type inference (notice that we had to set the type for aStringSetInAnInitializer, but not for aStringWithDefaultValue). Secondly, it is just a lot more readable.
Initializers are a lot like functions, and have basically the same syntax, except the use of the word “init” instead of func. There are a few nuances to how parameters are handled that we will go over in a bit, but they are still very similar.
As mentioned in my previous post Classes In Swift — An Introduction, for classes there is a default initializer generated. It is like our initializer above, that takes no parameters, but unlike ours, it does nothing. Therefore, it is only available to your classes if they have nothing that needs to be initialized. If all of the properties are provided default values, or there are no properties, then you can use the default initializer. Otherwise, you will either have to override it (like we did above), or make your own other initializers that take parameters to set up your properties.
One extra thing to note about this, normally, when a value is set, there are some functions called “property observers” that basically are willSet and didSet for whatever variable is being written to. When you set your properties you can set how their getters and setters work (like in my post Computed Properties in Swift), but for normal properties. You can read about them in my post Swift Property Observers. In the same place, you can also handle the willSet and didSet case (being called just before and just after setting respectively). When you set a value in either of the above ways (default values or initializers) in its own class, these observers are not called. Bear that in mind if you need to use property observers. If you subclass a type, and set a SUPERclass’s properties in the SUBclass’s initializer, the SUPERclass’s property observers will be called in that case. In all other cases though, setting a property in an initializer doesn’t call property observers.
So, how do you call one of these initializers? If you REALLY want, you could call the init directly like SampleClass.init(), but that seems needlessly verbose. So much so, that I’m writing in the prose section of this text, and not in a code block because it would be ugly to use initializers that way. You just call the initializer with the classes name, followed by the empty parenthesis, or the parameters if it takes any:
let ourClass = SampleClass()
It is a lot more complicated to say than to just show. That really is how simple it is to call them.
Initializer Parameters
You specify parameters for initializers the exact same way as you do for functions. There are a few differences in how they are handled, but the syntax is basically the same: An external name, followed by an internal name, a colon, and the type for that parameter, separated by commas. Again, easier to show than to write:
init(externalName internalName: String, secondParameterExt secondParameterInternal: String) { aStringSetInAnInitializer = internalName + " " + secondParameterInternal }
Please NEVER use names like that, it hurt a little bit typing it, but I thought showing explicitly what each part was was more important than my typographical comfort. With these terrible names, you would call this initializer like this:
let someObject = SampleClass(externalName: "Hey", secondParameterExt: "Swift")
Which would store “Hey Swift” in our aStringSetInAnInitializer variable.
If no external name is supplied, is to use the internal name as an external name. Say we had two initializers with these prototypes:
init(firstValue: Int, secondValue: Int) init(firstMultiplier: Int, secondMultiplier: Int)
Without external parameter names, how would you know the difference of which one was referred to if you called an init with 2 Ints as arguments? More importantly, how would the compiler know? Therefore it needs some way to differentiate between them, and it uses those internal names to do it, if you don’t provide external ones.
If you really want, you can force it to have no external parameter names. We even saw an example of this in a previous article Swift Strings,where it was used for String initializers that take numbers. You give it an external parameter name of an underscore “_” . That tells the compiler to not have an external name. By the way, just for fun, I tried that with our two initializers above, and I got the appropriate error “invalid redeclaration of ‘init’ “. So basically, it saw both of them with the same name (despite different internal names), and as such said that it was being redeclared.
There are much better uses for this. Apple’s iBook shows creating a Celsius object with no external parameter name, so that you just create your Celsius object with your Celsius temperature. Another example could be making a “Pet” object, taking its name as a parameter, like this:
class Pet { let name: String init(_ name: String) { self.name = name } } //Called like this let aPet = Pet("Kitty")
Using that underscore well can increase readability, or make it worse, it all depends on the context. In this case, there is probably enough context that “Kitty” is the pet’s name. For our multiplier or value initializers, that may not be the case (if we used the underscore).
You might have also noticed something. I wrote a let property, but did not give it a default value directly, I set it in the initializer. I had mentioned this in my previous post Classes In Swift — An Introduction, but it of course stands to reason that I should mention it here. Your object needs to have all of its properties set by the time the initializer is done. As such, you can give your constants new values all you want in the initializer, as long as all of them are all given valid values by the end of the initializer.
Once the constant property is set in an initializer (or anywhere else, for that matter), it cannot be changed again. If you try to in an initializer, you receive the appropriate error message, “Immutable value ‘self.thatValueName’ may only be initialized once.”
Conclusion
This is just the beginning of using initializers. We have predominantly covered how they are used with classes in this post. We will cover the differences for value types like structs or enumerations another day. There is also another very important part about initializers, how they are used with inheritance. This post has gotten quite long, so we will cover that another time as well.
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!