Today we will be learning about another aspect of Class Initializers. The Swift language has two different types of initializers they are called designated initializers and convenience initializers. These existed in Objective-C, but a few rules have changed in Swift, and a very helpful keyword was introduced. We will discuss how designated and convenience initializers are used, and how they can work together to get your classes ready for use.
Designated Initializers
Like most constructs in Swift, designated initializers are aptly named and do exactly what they say they do. They are the main initializers to be used for a class. A class must have one designated initializer, but it is not limited to one. It can have multiple if necessary, but most classes only have one.
Let’s take a look at a designated initializer:
init(sender: String, recipient: String) { self.sender = sender self.recipient = recipient timeStamp = Date() }
Does that look a bit familiar? If you’ve been following the blog for a while it should, that is the initializer for our Message class used in the previous articles Classes In Swift — An Introduction and Using a Nested Type in Swift. That syntax is to create a designated initializer. Our Message class only had one initializer, so it had to be the designated one. To create a designated initializer, it is just the init keyword, with the parameters afterwards, and then the code inside the curly braces.
Convenience Initializers
Convenience initializers are also aptly named, and they are initializers used to make initialization a bit easier. Designated initializers tend to set all of the properties up and let the user send in values for each. A convenience initializer often has some of those hard coded, and thus can take less parameters. The developer usually write’s a convenience initializer to set some defaults that are appropriate to a special use case.
The initializer above probably should have been a convenience initializer, since it sets timeStamp without giving the user any input to set one themselves. Like I said above, I think that designated initializers tend to have parameters for all properties they are setting (unless derived from some of the other parameters). This is just personal preference though, the above one is perfectly valid as a designated initializer, since we said in those posts that the timeStamp is set when the Message is created.
Nonetheless, let’s add a convenience initializer to our Swift app. Let’s say that this app could also send a message to oneself, to use it as a note perhaps. In this case, we wouldn’t need to set the sender and the recipient to different values. We would only be repeating ourself in the initializer when doing that, so lets write a convenience initializer that takes only one parameter:
convenience init(sender: String) { self.init(sender: sender, recipient: sender) }
The main difference in their syntax, is that convenience initializers have the convenience keyword before the init. Otherwise they are exactly the same. Once inside our convenience initializer that only takes the sender as a parameter, we just give that parameter to the designated initializer as both the sender and the recipient, since this Message is being used more like a note.
Rules for Designated and Convenience Initializers
Swift has three rules as to how designated and convenience initializers relate to each other. Instead of trying to paraphrase them, I’m just going to quote Apple’s iBook directly:
- A designated initializer must call a designated initializer from its immediate superclass.
- A convenience initializer must call another initializer from the same class.
- A convenience initializer must ultimately call a designated initializer.
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/us/jEUH0.l
So you can see our convenience initializer fulfilled rules 2 and 3. Our designated initializer was the top class in the hierarchy (the Message class), so there was no superclass initializer to call (for rule 1). If you recall though, it did have subclasses, like one for a TextMessage, let’s look at TextMessage’s designated initializer:
init(content: String, sender: String, recipient: String) { self.content = content super.init(sender: sender, recipient: recipient) }
You can see there, that it fulfills rule 1, calling “a designated initializer from its immediate superclass.” One thing to note, you see that we set our self.content property first, and then we called our superclass’s designated initializer. This is in contrast to how it was done in Objective-C (which had you call the superclass’s initializer first). A Swift class must initialize its own (non-inherited) properties before it calls its superclass’s designated initializer. You can then set inherited properties after calling the superclass’s designated initializer, if you wish.
This appears to be due to how class inheritance works, according to one of the WWDC videos (specifically Intermediate Swift). For instance, if a superclass calls a method that the subclass overrode, the superclass will actually call the subclass’s implementation (since it was overridden). If we didn’t fully initialize our subclass’s properties, and that overridden function relied on them, we would have a bit of a problem.
If you are unsure what I am talking about with the self.content setting there, the whole code for these classes is shown in the previous post Classes In Swift — An Introduction.
There is a nuance to these rules. In rule 2, you see that it just says that a convenience initializer must call another initializer. It doesn’t say dedicated initializer there, just any initializer. If you want, you can have several convenience initializers that handle a small part of the initialization, and just call them in a chain. However though, you still have to follow rule 3. If you want to call multiple convenience initializers, they have to eventually lead to one that calls a designated initializer. Here is an example fulfilling all 3 rules in our TextMessage class:
init(content: String, sender: String, recipient: String) { self.content = content //Rule 1: Calling designated Initializer from immediate superclass super.init(sender: sender, recipient: recipient) } convenience init() { //Rule 2: Calling another initializer in same class self.init(content: "") } convenience init(content: String) { //Rule 2: Calling another initializer in same class self.init(content: content, sender: "Myself") } convenience init(content: String, sender: String) { //Rule 2 and 3: Calling the Designated Initializer in same class self.init(content: content, sender: sender, recipient: sender) }
You can see the no-parameter convenience initializer calls the single parameter convenience initializer with a blank content Swift String. That convenience initializer then calls the 2 parameter convenience initializer with the provided content String, and sets the sender parameter to the String “Myself”. Finally that convenience initializer calls the designated initializer for this class with the provided content String, and sets the sender and recipient parameters to the provided “sender” parameter. That fulfills rules 2 and 3. Then there is of course the designated initializer we mentioned earlier that calls its superclass’s designated initializer, fulfilling rule 1.
Now, you probably would not want to make such a silly chain of convenience initializers, setting each part individually like that, at least not very often. Nonetheless though, you can, and there are definitely use cases for convenience initializers setting up individual parts. If nothing else, it lets you write the code for setting those defaults only once, if they must be set in the initializers.
Now, we made that convenience initializer earlier for setting the sender and receiver to be the same for notes, why not make some sort of NoteMessage subclass and call that? Well….
class NoteMessage: Message { let content: String init(content: String, theUser: String) { self.content = content super.init(sender: theUser) //Error!: Must call a designated initializer of the superclass 'Message' } }
Awww….. I guess we’ll have to slog through and set both the sender and receiver ourselves in Message’s designated initializer. Remember rule 1, “A designated initializer must call a designated initializer from its immediate superclass.” We tried calling a convenience initializer, and it did not take kindly to that.
I am not entirely sure why you cannot call a superclass’s convenience initializer in Swift. Since the convenience initializer for the superclass eventually calls a designated initializer of that superclass, that should have everything fully initialized. It may be simply that designated initializers just aren’t allowed to call convenience initializers at all, since doing so in their same class would cause circular calls (designated initializer -> convenience initializer -> designated initializer, etc). That might be it, but I am not sure.
It might be just to force the programmer to think of what default is appropriate for your class to set to its superclass’s properties. In the case of our class, the superclass’s convenience initializer would have been fine, but that may not always be the case. In other words, it may not be a technical limitation, but just one to make the programmer think a bit more about how they are initializing their properties.
Or it could just be a limitation of Swift that will be changed later, who knows? For now though, the rule stands, that “a designated initializer must call a designated initializer from its immediate superclass.”
Conclusion
Designated initializers perform the actual initialization for a class’s properties. Convenience initializers let you program defaults into simpler initializers with less input parameters, and they hand off the actual initialization back to the designated initializers. You don’t have to have any convenience initializers if you wish, but they are quite useful in certain situations.
I am particularly happy about the addition of the convenience keyword in Swift. In Objective-C, as far as i know, the only way to know which was the designated initializer was to look in the comments in the header file (assuming you do not have access to the code). If they did not comment about it, and you don’t have the code, I think you would just have to guess. If you had the code, the designated initializer is the one that calls the superclass’s initializer (similar to rule 1 above).
Clearly marking a convenience initializer as such just makes it easier to know. In our class, it was rather obvious, but it may not be so obvious for all classes.
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!