Exploring Swift Initializers
What Is An Initializer?
An initializer is a special type of method in a Swift struct, class, or enum that is responsible for making sure a newly created instance of the struct, class, or enum is fully initialized before they are ready to be used. They play the same role as that of a “constructor” in Java and Groovy. If you’re familiar with Objective-C, you should note that Swift initializers differ from Objective-C initializers in that they do not return a value.
Subclasses Generally Don’t Inherit Initializers
One of the first things to keep in mind is that “Swift subclasses do not inherit their superclass initializers by default”, according to the language guide. (The guide explains that there are scenarios where superclass initializers are automatically inherited. We’ll cover those exceptional scenarios later). This is consistent with how Java (and by extension, Groovy) works. Consider the following:
Just as with Java & Groovy, it makes sense that this is not allowed (although, as with most things, there may be some argument on this point. See this StackOverflow post). If it were allowed, the initialization of the “instrument” property of Musician would be bypassed, potentially leaving your Musician instance in an invalid state. However, with Groovy I normally wouldn’t bother with writing initializers (i.e., constructors). Rather, I’d just use the map constructor Groovy provides implicitly, which allows you to freely pick and choose which properties you want to set upon construction. For example, the following is perfectly valid Groovy code:
Notice that you can include any property, including those provided by superclasses, but you don’t have to specify all (or any) of them, and they don’t have to be specified in any particular order. This kind of ultra-flexible initializer is not provided by Swift. The closest thing in Swift is the automatically-provided memberwise initializers for structs. But the order of the arguments in a memberwise initializer is significant, even though they’re named, and depends on the order in which they are defined:
Anyway, back to classes – Groovy’s philosophy regarding post-construction object validity is clearly very different from Swift’s. This is just one of many ways in which Groovy differs from Swift.
Designated vs Convenience Initializers
Before we get too far down the rabbit hole, we should clarify an important concept: In Swift, an initializer is categorized as being either a designated or a convenience initializer. I’ll try to explain the difference both conceptually and syntactically. Each class must have at least one designated initializer, but may have several (“designated” does not imply “single”). A designated initializer is considered a primary initializer. They’re the head-honchos. They are ultimately responsible for making sure that all properties are initialized. Because of that responsibility, it can sometimes become a pain to use a designated initializer all the time, since they may require several arguments. Imagine working with a class that has several properties, and you need to create several instances of that class that are almost identical except for a one or two properties. (For the sake of argument, let’s also assume that there are no sensible defaults that could have been assigned to properties when they are declared). For example, let’s say our Person class also had eatsFood and enjoysMusic properties. Of course, those two things would be set to true most of the time, but you never know. Take a look:
Now our Person class has four properties that have to be set, and we have a designated initializer that can do the job. The designated initializer is that first one, the one that takes 4 arguments. Most of the time, those last two arguments are going to have the value “true”. It’d be a pain to have to keep specifying them every time we want to create a typical Person. That’s where the last two initializers come in, the ones marked with the convenience modifier. This pattern should look familiar to a Java developer. If you have a constructor that takes more arguments than you really need to deal with all the time, you can write simplified constructors that take a subset of those arguments and provide sensible defaults for the others. The convenience initializers must delegate either to another, perhaps less-convenient, convenience initializer or to a designated initializer. Ultimately a designated initializer must get involved. Further, if this is a subclass, the designated initializer must call a designated initializer from it’s immediate superclass.
One real-world example for the use of the convenience modifier comes from UIKit’s UIBezierPath class. I’m sure you can imagine there are several ways of specifying a path. As such, UIBezierPath provides several convenience initializers:
public convenience init(rect: CGRect)
public convenience init(ovalInRect rect: CGRect)
public convenience init(roundedRect rect: CGRect, cornerRadius: CGFloat)
public convenience init(roundedRect rect: CGRect, byRoundingCorners corners: UIRectCorner, cornerRadii: CGSize)
public convenience init(arcCenter center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool)
public convenience init(CGPath: CGPath)
Earlier, I said a class may have multiple designated initializers. So what does that look like? One important difference, enforced by the compiler, between designated initializers and convenience initializers is that designated initializers may not delegate to another initializer in the same class (but must delegate to a designated initializer in it’s immediate superclass). Look at the second initializer of Person, the one that takes a single argument named “unDead”. Since this initializer is not marked with the convenience modifier, Swift treats it as a designated initializer. As such, it may not delegate to another initializer in Person. Try commenting out the first four lines, and uncommenting the last line. The compiler will complain, and XCode should try to help you out by suggesting that you fix it by adding the convenience modifier.
Now consider the Musician subclass of Person. It has a single initializer, and it must therefore be a designated initializer. As such, it must call a designated initializer of the immediate superclass, Person. Remember: while designated initializers can’t delegate to another initializer of the same class, convenience initializers must do so. Also, a designated initializer must call a designated initializer of it’s immediate superclass. See the language guide for more detail (and pretty graphics).
As the Swift language guide explains, there are two initialization phases. The phases are demarcated by the call to the superclass designated initializer. Phase 1 is prior to the call to the superclass designated initializer, phase 2 is after. A subclass must initialize all of it’s OWN properties in phase 1, and it may NOT set any properties defined by the superclass until phase 2.
Here is a code sample, adapted from a sample provided in the language guide, showing that you must initialize the OWN properties of a subclass before invoking the superclass designated initializer. You may not access properties provided by the superclass until after invoking the superclass designated initializer. Finally, you may not modify constant stored properties after the superclass designated initializer has been invoked.
Overriding an Initializer
Now that we’re convinced that subclasses generally don’t inherit initializers, and we’re clear on the meaning of and distinction between designated and convenience initializers, let’s consider what happens when you want a subclass to override an initializer from it’s immediate superclass. There are four scenarios that I’d like to cover, given that there are two types of initializer. So let’s take them one by one, with simple code examples for each case:
A designated initializer that matches a superclass designated initializer
This is a typical scenario. When you do this, you must apply the override modifier. Note that this scenario is in effect even when you’re “overriding” an automatically provided default initializer (i.e., when the superclass does not define any explicit initializer. In this case, Swift provides one implicitly. Java developers should be familiar with this behavior). This automatically-provided default initializer is always a designated initializer.
A designated initializer that matches a superclass convenience initializer
Now let’s suppose you want to add a designated initializer to your subclass that happens to match a convenience initializer in the parent. By the rules of initializer delegation laid out in the language guide, your subclass designated initializer must delegate up to a designated initializer of the immediate superclass. That is, you may not delegate up to the parent’s matching convenience initializer. For the sake of argument, also suppose that subclass does not qualify to inherit superclass initializers. Then, since you could never create an instance of your subclass by directly invoking the superclass convenience initializer, that matching convenience initializer is not, and could never be, involved in the initialization process anyway. Therefore, you’re not really overriding it, and the override modifier does not apply.
A convenience initializer that matches a superclass designated initializer
In this scenario, imagine you have a subclass that adds its own properties whose default value can be (but doesn’t have to be) computed from the values assigned to one or more parent-class properties. Suppose you also only want to have one designated initializer for your subclass. You could add a convenience initializer to your subclass whose signature matches that of a designated initializer of the parent class. In this case, your new initializer would need both the convenience and override modifiers. Here is a valid code sample to illustrate this case:
A convenience initializer that matches a superclass convenience initializer
If you want to add a convenience initializer to your subclass that happens to match the signature of a convenience initializer of your superclass, just go right ahead. As I explained above, you can’t really override convenience initializers anyway. So you’d include the convenience modifier, but omit the override modifier, and treat it just like any other convenience initializer.
One key takeaway from this section is that the override modifier is only used, and must be used, if you are overriding a superclass designated initializer. (Minor clarification to make here: if you’re overriding a required initializer, then you would use the required modifier instead of the override modifier. The required modifier implies the override modifier. See the Required Initializers section below).
When Initializers Are Inherited
Now for the aforementioned scenarios where superclass initializers are inherited. As the Swift Language Guide explains, if your subclass provides default values for all of it’s own properties at declaration, and does not define any of its own designated initializers, then it will automatically inherit all of its superclass designated initializers. Or, if your subclass provides an implementation of all of the superclass designated initializers, then it automatically inherits all of the superclass convenience initializers. This is consistent with the rule in Swift that initialization of classes (and structures) must not leave stored properties in an indeterminate state.
I stumbled across some “interesting” behavior while experimenting with convenience initializers, designated initializers, and the inheritance rules. I found that it is possible to setup a vicious cycle inadvertently. Consider the following example:
The RecipeIngredient class overrides all of the Food class designated initializers, and therefore it automatically inherits all of the superclass convenience initializers. But the Food convenience initializer reasonably delegates to it’s own designated initializer, which has been overridden by the RecipeIngredient subclass. So it is not the Food version of that init(name: String) initializer that is invoked, but the overridden version in RecipeIngredient. The overridden version takes advantage of the fact that the subclass has inherited Food’s convenience initializer, and there it is – you have a cycle. I don’t know if this would be considered a programmer-mistake or a compiler bug (I reported it as a bug: https://bugs.swift.org/browse/SR-512 ). Imagine that Food is a class from a 3rd party, and you don’t have access to the source code so you don’t know how it’s actually implemented. You wouldn’t know (until runtime) that using it in the way shown in this example would get you trapped in a cycle. So I think it’d be better if the compiler helped us out here.
Imagine you’ve designed a class that has certain invariants, and you would like to enforce those invariants from the moment an instance of the class is created. For example, maybe you are modeling an Invoice and you want to make sure the amount is always non-negative. If you added an initializer that takes an amount argument of type Double, how could you make sure that the you don’t violate your invariant? One strategy is to simply check if the argument is non-negative. If it is, use it. Otherwise, default to 0. For example:
This would work, and may be acceptable if you document what your initializer is doing (especially if you plan on making your class available to other developers). But you might have a hard time defending that strategy, since it does kind of sweep the issue under the rug. Another approach that is supported by Swift would be to let initialization fail. That is, you’d make your initializer failable.
As this article describes, failable initializers were added to Swift as a way to eliminate or reduce the need for factory methods, “which were previously the only way to report failure” during object construction. To make an initializer failable, you simply append the ? or ! character after the init keyword (i.e., init? or init! ). Then, after all the properties have been set and all the other rules regarding delegation have been satisfied, you’d add some logic to verify that the arguments are valid. If they are not valid, you trigger an initialization failure with return nil. Note that this does not imply that the initializer is ever returning anything. Here’s how our Invoice class might look with a failable initializer:
Notice anything different about how we’re using the result of the object creation? It’s like we’re treating it as an optional, right? Well, that’s exactly what we’re doing! When we use a failable initializer, we’ll either get nil (if an initialization failure was triggered) or an Optional(Invoice). That is, if initialization was successful we’ll end up with an Optional that wraps the Invoice instance we’re interested in, so we have to unwrap it. (As an aside, note that Java also has Optionals as of Java 8).
Failable initializers are just like the other types of initializers we’ve discussed with respect to overriding and delegation, designated vs convenience, etc… In fact, you can even override a failable initializer with a nonfailable initializer. You cannot, however, override a nonfailable initializer with a failable one.
You may have noticed failable initializers from dealing with UIView or UIViewController, which both provide a failable initializer init?(coder aDecoder: NSCoder). This initializer is called when your View or ViewController is loaded from a nib. It is important to understand how failable initializers work. I strongly recommend you read through the Failable Initializers section of the Swift language guide for a thorough explanation.
The required modifier is used to indicate that all subclasses must implement the affected initializer. On the face of it, that sounds pretty simple and straightforward. It can get a bit confusing at times if you don’t understand how the rules regarding initializer inheritance discussed above come into play. If a subclass meets the criteria by which superclass initializers are inherited, then the set of inherited initializers includes those marked required. Therefore, the subclass implicitly satisfies the contract imposed by the required modifier. It does implement the required initializer(s), you just don’t see it in source code.
If a subclass provides an explicit (i.e., not inherited) implementation of a required initializer, then it is also overriding the superclass implementation. The required modifier implies override, so the override modifier is not used. You may include it if you want to, but doing so would be redundant and XCode will nag you about it.
The Swift language guide doesn’t say much about the required modifier, so I prepared a code sample (see below) with comments to explain it’s purpose and describe how it works. For more information, see this article by Anthony Levings.
Special Case: Extending UIView
One of the things that prompted me to dig deep into Swift initializers was my attempt to figure out a way to create a set of designated initializers without duplicating initialization logic. For example, I was working through this UIView tutorial by Ray Wenderlich, converting his Objective-C code into Swift as I went (you can take a look at my Swift version here). If you look at that tutorial, you’ll see that his RateView subclass of UIView has a baseInit method that both of the designated initializers use to perform common initialization tasks. That seems like a good approach to me – you don’t want to duplicate that stuff in each of those initializers. I wanted to recreate that technique in my Swift version of RateView. But I found it difficult because a designated initializer cannot delegate to another initializer in the same class and cannot call methods of its own class until after it delegates to the superclass initializer. At that point, its too late to set constant properties. Of course, you could work around this limitation by not using constants, but that is not a good solution. So I figured it was best to just provide default values for the stored properties where they are declared. That’s still the best solution that I currently know of. However, I did figure out an alternative technique that uses initializers.
Take a look at the following example. This is a snippet from RateViewWithConvenienceInits.swift, which is an alternate version of my Swift port of RateView. Being a subclass of UIView that does not provide default values for all of it’s own stored properties at declaration, this alternate version of RateView must at least provide an explicit implementation of UIView’s required init?(coder aDecoder: NSCoder) initializer. We will also want to provide an explicit implementation of UIView’s init(frame: CGRect) initializer to make sure that the initialization process is consistent. We want our stored properties to be set up the same way regardless of which initializer is used.
Notice that I added the convenience modifier to the overridden versions of UIView’s initializers. I also added a failable designated initializer to the subclass, which both of the overridden (convenience) initializers delegate to. This single designated initializer takes care of setting up all the stored properties (including constants – I did not have to resort to using var for everything). It works, but I think it’s pretty kludgy. I’d prefer to just provide default values for my stored properties where they are declared, but it’s good to know this option exists if needed.
Thanks for reading! I hope you found this article useful. Please leave a comment if you have any questions or helpful suggestions.