In Part 1 of this tutorial, you learned about initialization in Swift and applied it by creating a launch sequence data module for a manned NASA mission to Mars. You implemented initializers for structs, tried initializer delegation, and learned when and why to use failable and throwable initializers.
But there’s more to learn, and NASA still needs your help! In this second part of the tutorial, you’ll learn about class initialization in Swift. Class initialization is very different from class initialization in Objective-C, and when writing class initializers in Swift for the first time, you’ll encounter many different compiler errors. This tutorial walks you through potential issues and gives you all the understanding necessary to avoid these compiler errors.
In this part of the tutorial, you’ll:
- see the initializer delegation equivalent for classes,
- learn the extra rules about failable and throwable initialization for classes,
- learn how classes delegate initialization up the class hierarchy, and
- learn how classes inherit initializers
Make sure to complete Part 1 first, because here you’ll build on what you learned there.
Getting Started
You can either continue working in the playground from Part 1, or create a new playground. The code in this part doesn’t depend on any code from Part 1, but Foundation is required for this tutorial, so make sure the playground imports Foundation. Importing UIKit or AppKit meets this requirement.
Failing or Throwing from Class Initializers
The countdown to Mars continues! In the first part, you learned about failable and throwing initializers, and now you’ll see how that process differs for class initializers.
The launch system needs a way to represent the launch site, so start by implementing the following class:
class LaunchSite { let name: String let coordinates: (String, String) init(name: String, coordinates: (String, String)) { self.name = name self.coordinates = coordinates } } |
LaunchSite
is a simple two-property class with a basic initializer. coordinates
is declared as a tuple of two string values.
To improve the initializer, try changing the initializer to a failable initializer by checking if any of the string values are empty.
init?(name: String, coordinates: (String, String)) { guard !name.isEmpty && !coordinates.0.isEmpty && !coordinates.1.isEmpty else { return nil } self.name = name self.coordinates = coordinates } |
Note that this will not compile.
The initializer above would be a-ok if LaunchSite
was declared as a struct. But unlike structs, classes will not let you return nil
or throw during Phase 1 of initialization. This means that every single stored property must be given an initial value first.
Note: This will probably be fixed in a future version of Swift; now that Swift is open source, you can already see the change with the commit message “Add support for early return from designated initializers.” in the repository!
To fix the compiler error, move the guard
statement to the end of the initializer:
init?(name: String, coordinates: (String, String)) { self.name = name self.coordinates = coordinates guard !name.isEmpty && !coordinates.0.isEmpty && !coordinates.1.isEmpty else { return nil } } |
Now the return nil
gets called in Phase 2 of initialization. This is an easy fix because you have values passed in that you can set regardless of success or failure. However, it’s not always this easy. Sometimes on failure you don’t have values to set, so following this rule can be less than ideal — especially if you have a class with a lot of stored properties. Later in this tutorial, you’ll work through an example that demonstrates a streamlined way of handling this situation.
As you can see, unlike the Objective-C compiler, the Swift compiler cares a lot about the order in which things occur within class initializers. This is just one of many potential compiler errors you will learn to avoid.

You’ll learn to avoid asteroids too. Image credit: ESA
Initializer Delegation
Lift-off to Mars is only a year away! You’ve completed all but one very important task before you can ship the launch systems code: you need to implement models that represent different rocket components. Specifically, the tanks that hold rocket fuel and liquid oxygen.
Along the way, you’ll see the difference in how initializer delegation works with classes.
Designated Initializers
Add the RocketComponent
class to the end of your playground:
class RocketComponent { let model: String let serialNumber: String let reusable: Bool // Init #1a - Designated init(model: String, serialNumber: String, reusable: Bool) { self.model = model self.serialNumber = serialNumber self.reusable = reusable } } |
RocketComponent
is another simple class. It has three constant stored properties and a designated initializer.
Remember delegating initializers from Part 1 of this tutorial? Recall that a chain of delegating initializers eventually ends by a call to a non-delegating initializer. In the world of classes, a designated initializer is just a fancy term for a non-delegating initializer. Just like with structures, these initializers are responsible for providing initial values to all non-optional stored properties declared without a default value.
The comment “// Init #1a – Designated” is not required, but as you go further in this tutorial, these types of comments will help keep your initializers organized.
At the bottom of the playground, write the following code to see the designated initializer in action:
let payload = RocketComponent(model: "RT-1", serialNumber: "234", reusable: false) |
Convenience Initializers
What about delegating initializers? They have a fancy name too in the world of classes :]. Implement the following convenience initializer within RocketComponent
:
// Init #1b - Convenience convenience init(model: String, serialNumber: String) { self.init(model: model, serialNumber: serialNumber, reusable: false) } |
Notice how this looks just like a structure’s delegating initializer. The only difference here is that you have to prefix the declaration with the convenience
keyword.
At the end of the playground, use the convenience initializer to create another instance of RocketComponent
:
let fairing = RocketComponent(model: "Serpent", serialNumber: "0") |
Similar to how they work with structs, convenience initializers let you have simpler initializers that just call through to a designated initializer.
Failing and Throwing Strategy
There’s an art to designing failable and throwing initializers — thankfully, it doesn’t require paint. At first, you might find yourself writing code that’s difficult to read, so below are some strategies to maximize your failable and throwing initializer legibility.
Failing and Throwing from Designated Initializers
Let’s say that all rocket components in this mission report their model and serial number as a formatted string: the model followed by a hyphen followed by the serial number. For example, “Athena-003”. Implement a new RocketComponent
failable designated initializer that takes in this formatted identifier string.
// Init #1c - Designated init?(identifier: String, reusable: Bool) { let identifierComponents = identifier.componentsSeparatedByString("-") guard identifierComponents.count == 2 else { return nil } self.reusable = reusable self.model = identifierComponents[0] self.serialNumber = identifierComponents[1] } |
You get an error.
This is how someone might build this initializer, yet this version does not compile. Remember, you cannot fail or throw without first setting all stored properties to an initial value.
Last time, we solved this by simply moving the guard statement to the end. Try it here:
// Init #1c - Designated init?(identifier: String, reusable: Bool) { let identifierComponents = identifier.componentsSeparatedByString("-") self.reusable = reusable self.model = identifierComponents[0] self.serialNumber = identifierComponents[1] guard identifierComponents.count == 2 else { return nil } } |
That looks pretty good, but you’re checking for the number of components after you use them! If there aren’t enough components, it’s possible that identifierComponents[1]
won’t exist and you’ll be trying to access something out of bounds in the array, leading to a runtime trap.
Fix the initializer by reorganizing the logic flow as follows:
// Init #1c - Designated init?(identifier: String, reusable: Bool) { self.reusable = reusable let identifierComponents = identifier.componentsSeparatedByString("-") guard identifierComponents.count == 2 else { self.model = "" self.serialNumber = "" return nil } self.model = identifierComponents[0] self.serialNumber = identifierComponents[1] } |
Yes, this works, but it’s harder to read. Thankfully, there is a better way! That’s coming up next.
Before moving on, instantiate the following constants to see this initializer at work:
let component = RocketComponent(identifier: "R2-D21", reusable: true) let nonComponent = RocketComponent(identifier: "", reusable: true) |
Notice in the sidebar how nonComponent
is correctly set to nil
because the identifier does not follow the model-serial number format.
Failing and Throwing From Convenience Initializers
Replace the initializer you just wrote with the following implementation:
// Init #1c - Convenience convenience init?(identifier: String, reusable: Bool) { let identifierComponents = identifier.componentsSeparatedByString("-") guard identifierComponents.count == 2 else { return nil } self.init(model: identifierComponents[0], serialNumber: identifierComponents[1], reusable: reusable) } |
Wow, isn’t that a lot easier to read? It’s also more concise. Because convenience initializers are not responsible for setting all property values, they are not subject to designated initializers’ fail/throw rules.
When writing initializers, make the designated initializers non-failable and have them set all the properties. Then your convenience initializers can have the failiable logic and delegate the actual initialization to the designated initializer.
Note that there is a downside to this approach, relating to inheritance. Don’t worry, we’ll explore how to overcome this downside in the last section of this tutorial.
Subclassing
That’s all there is to know about class initialization for root classes. Root classes, which don’t inherit from other classes, are what you’ve been working with so far. The rest of this tutorial focuses on class initialization with inheritance.
Fair warning: this is where things get bumpy! Class initialization is a lot more complicated than struct initialization, because only classes support inheritance.
Differences from Objective-C
Note: If you’ve never seen Objective-C code before, don’t worry — you can still read this section!
If you’ve programmed in Objective-C, Swift class initialization will feel restrictive. Swift defines many new and not-so-obvious initialization rules with regards to classes and inheritance. Don’t be surprised if you run into unexpected compiler errors that enforce these initialization rules.
Don’t write any of this Objective-C code in your playground — you’ll hop back into your Swift playground soon enough, but first I’ll discuss Objective-C to show a naive approach to initialization.
Why are there so many rules with Swift? Consider the following Objective-C header:
@interface RocketComponent : NSObject @property(nonatomic, copy) NSString *model; @property(nonatomic, copy) NSString *serialNumber; @property(nonatomic, assign) BOOL reusable; - (instancetype)init; @end |
Assume the initializer does not set any of RocketComponent
‘s properties. Objective-C will automatically initialize all properties to an empty-ish value, such as NO
or 0 or nil
. Bottom line: this code is capable of creating a fully initialized instance.
Note that the equivalent class with non-optional typed properties would not compile in Swift because the compiler would not know what values to use to initialize the properties. Swift does not initialize properties automatically to empty values; it only initializes optional typed properties automatically to nil. As you saw in Part 1 of this tutorial, programmers are responsible for defining initial values for all non-optional stored properties; otherwise the Swift compiler will complain.
This distinction between Objective-C and Swift initialization behavior is fundamental to understanding the long list of Swift class initialization rules. Say you update the Objective-C initializer to take initial values for each property:
@interface RocketComponent : NSObject @property(nonatomic, copy) NSString *model; @property(nonatomic, copy) NSString *serialNumber; @property(nonatomic, assign) BOOL reusable; - (instancetype)initWithModel:(NSString *)model serialNumber:(NSString *)serialNumber reusable:(BOOL)reusable; @end |
RocketComponent
now knows how to initialize an instance without using empty values. This time, the Swift equivalent would compile successfully.
Adding Properties to Subclasses
Check out the following RocketComponent
Objective-C subclass and its instantiation:
@interface Tank : RocketComponent @property(nonatomic, copy) NSString *encasingMaterial; @end Tank *fuelTank = [[Tank alloc] initWithModel:@"Athena" serialNumber:@"003" reusable:YES]; |
Tank
introduces a new property, but does not define a new initializer. That’s okay, because the new property will be initialized to nil
per Objective-C behavior. Notice how fuelTank
is a Tank
initialized by the initializer implemented in RocketComponent
, the superclass. Tank
inherits initWithModel:serialNumber:reusable:
from RocketComponent
.
This is where things really start to break down in Swift. To see it, write in your playground the equivalent Tank
subclass from above using Swift. Note that this code will not compile.
class Tank: RocketComponent { let encasingMaterial: String } |
Notice how this subclass introduces a new stored property, encasingMaterial
. This code does not compile because Swift does not know how to fully initialize an instance of Tank
. Swift needs to know what value should be used to initialize the new encasingMaterial
property.
You have three options to fix the compiler error:
- Add a designated initializer that calls or overrides the designated initializer for the superclass
RocketComponent
. - Add a convenience initializer that calls the designated initializer for the superclass
RocketComponent
. - Add a default value for the stored property.
Let’s go for option 3 since it’s the simplest. Update the Tank
subclass by declaring “Aluminum” as the default property value for encasingMaterial
:
class Tank: RocketComponent { let encasingMaterial: String = "Aluminum" } |
It compiles and runs! Not only that, but your effort is rewarded with a bonus: you can take advantage of the initializers inherited from RocketComponent
without adding one to Tank
.
Using Inherited Initializers
Instantiate a tank with this code:
let fuelTank = Tank(model: "Athena", serialNumber:"003", reusable: true) let liquidOxygenTank = Tank(identifier: "LOX-012", reusable: true) |
That’s an easy way of solving the missing initializer compiler error. This works just as it would in Objective-C. However, most of the time your subclasses will not automatically inherit initializers from their superclasses. You will see this in action later.
Understanding the impact of adding stored properties within a subclass is critical to avoiding compiler errors. In preparation for the next section, comment out the fuelTank
and liquidOxygenTank
instantiations:
// let fuelTank = Tank(model: "Athena", serialNumber:"003", reusable: true) // let liquidOxygenTank = Tank(identifier: "LOX-012", reusable: true) |
Adding Designated Initializers to Subclasses
encasingMaterial
from Tank
is a constant property with a default value of "Aluminum"
. What if you needed to instantiate another tank that is not encased in Aluminum? To accommodate this requirement, remove the default property value and make it a variable instead of a constant:
var encasingMaterial: String |
The compiler errors out with, “Class ‘Tank’ has no initializers”. Every subclass that introduces a new non-optional stored property without a default value needs at least one designated initializer. The initializer should take in the initial value for encasingMaterial
in addition to initial values for all the properties declared in the RocketComponent
superclass. You have already built designated initializers for root classes, and it’s time to build one for a subclass.
Let’s build out Option 1 from the “Adding properties to subclasses” section: add a designated initializer that calls or overrides the designated initializer for the superclass RocketComponent
.
Your first impulse might be to write the designated initializer like this:
init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) { self.model = model self.serialNumber = serialNumber self.reusable = reusable self.encasingMaterial = encasingMaterial } |
This looks like all the designated initializers you have built throughout this tutorial. However, this code won’t compile, because Tank
is a subclass. In Swift, a subclass can only initialize properties it introduces. Subclasses cannot initialize properties introduced by superclasses. Because of this, a subclass designated initializer must delegate up to a superclass designated initializer to get all of the superclass properties initialized.
Add the following designated initializer to Tank
:
// Init #2a - Designated init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) { self.encasingMaterial = encasingMaterial super.init(model: model, serialNumber: serialNumber, reusable: reusable) } |
The code is back to compiling successfully! This designated initializer has two important parts:
- Initialize the class’s own properties. In this case, that’s just
encasingMaterial
. - Delegate the rest of the work up to the superclass designated initializer,
init(model:serialNumber:reusable:)
.
Two-Phase Initialization Up a Class Hierarchy
Recall that two-phase initialization is all about making sure delegating initializers do things in the correct order with regards to setting properties, delegating and using a new instance. So far you’ve seen this play out for struct delegating initializers and for class convenience initializers, which are essentially the same.
There’s one more kind of delegating initializer: the subclass designated initializer. You built one of these in the previous section. The rule for this is super easy: you can only set properties introduced by the subclass before delegation, and you can’t use the new instance until phase 2.
To see how the compiler enforces two-phase initialization, update Tank
‘s initializer #2a as follows:
// Init #2a - Designated init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) { super.init(model: model, serialNumber: serialNumber, reusable: reusable) self.encasingMaterial = encasingMaterial } |
This fails to compile because the designated initializer is not initializing all the stored properties this subclass introduces during phase 1.
Update the same initializer like this:
// Init #2a - Designated init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) { self.encasingMaterial = encasingMaterial self.model = model + "-X" super.init(model: model, serialNumber: serialNumber, reusable: reusable) } |
The compiler will complain that model
is not variable. Don’t actually do this, but if you were to change the property from a constant to a variable, you would then see this compiler error:
This errors out because the subclass designated initializer is not allowed to initialize any properties not introduced by the same subclass. This code attempts to initialize model
, which was introduced by RocketComponent
, not Tank
.
You should now be well equipped to recognize and fix this compiler error.
To prepare for the next section, update Tank's
2a initializer to how it was before this section:
// Init #2a - Designated init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) { self.encasingMaterial = encasingMaterial super.init(model: model, serialNumber: serialNumber, reusable: reusable) } |
Un-inheriting Initializers
Now that you’ve returned to a compiling version of Tank
, uncomment the fuelTank
and liquidOxygenTank
instantiations from before:
let fuelTank = Tank(model: "Athena", serialNumber:"003", reusable: true) let liquidOxygenTank = Tank(identifier: "LOX-012", reusable: true) |
fuelTank
and liquidOxygenTank
no longer instantiate, so this code results in a compiler error. Recall that this compiled and ran fine before adding the designated initializer to Tank
. What’s going on?
Both instantiations are attempting to create a Tank
using initializers that belong to RocketComponent
, which is Tank
‘s superclass. The problem is that RocketComponent
‘s initializers only know how to initialize the properties introduced by RocketComponent
. These initializers have no visibility into properties introduced by subclasses. Therefore RocketComponent
‘s initializers are incapable of fully initializing a Tank
, even though Tank
is a subclass.
This code would work fine in Objective-C because Tank
‘s encasingMaterial
property would be initialized to nil
by the runtime before invoking any initializer. So in Objective-C, there’s no harm in Tank
inheriting all of RocketComponent
‘s initializers, because they are capable of fully initializing a Tank
.
Even though automatic initializer inheritance is allowed in Objective-C, it’s clearly not ideal because Tank
might assume that encasingMaterial
is always set to a real string value. While this probably won’t cause a crash, it might cause unintended side effects. Swift does not allow this situation because it’s not safe, so as soon as you build a designated initializer for a subclass, the subclass stops inheriting all initializers, both designated and convenience.
In preparation for the next section, comment out the fuelTank
and liquidOxygenTank
instantiations again:
// let fuelTank = Tank(model: "Athena", serialNumber:"003", reusable: true) // let liquidOxygenTank = Tank(identifier: "LOX-012", reusable: true) |
The Initialization Funnel
To recap: convenience initializers delegate across to other convenience initializers until delegating to a designated initializer.
Designated initializers in subclasses must delegate up to a designated initializer from the immediate superclass.
Keep in mind that a designated initializer in a subclass cannot delegate up to a superclass convenience initializer.
Also, a convenience initializer in a subclass cannot delegate up to a superclass convenience initializer. Convenience initializers can only delegate across the class in which they are defined.
Each class in a class hierarchy either has:
- no initializers
- one or more designated initializer
- one or more convenience initializer and one or more designated initializer
Consider the following class diagram:
Say you want to initialize an instance of class D using D’s designated initializer. Initialization will funnel up through C’s designated initializer and up to A’s designated initializer.
Or say you want to initialize an instance of E using E’s second convenience initializer. Initialization will move across to E’s first convenience initializer, then to E’s designated initializer. At that point, initialization will funnel up through D’s and then C’s designated initializers, and finally up to A’s designated initializer.
Class initialization begins moving across convenience initializers, if any, then proceeds to a designated initializer, then finally goes up through each designated initializer for each superclass.
Delegating Up the Funnel
Add the following LiquidTank
subclass to the end of your playground:
class LiquidTank: Tank { let liquidType: String // Init #3a - Designated init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String, liquidType: String) { self.liquidType = liquidType super.init(model: model, serialNumber: serialNumber, reusable: reusable, encasingMaterial: encasingMaterial) } } |
You will use this class to trace initialization through the funnel. It has your standard subclass initializer: sets its own properties, and then calls the initializer on super
.
Add the following two convenience initializers to LiquidTank
:
// Init #3b - Convenience convenience init(model: String, serialNumberInt: Int, reusable: Bool, encasingMaterial: String, liquidType: String) { let serialNumber = String(serialNumberInt) self.init(model: model, serialNumber: serialNumber, reusable: reusable, encasingMaterial: encasingMaterial, liquidType: liquidType) } // Init #3c - Convenience convenience init(model: String, serialNumberInt: Int, reusable: Int, encasingMaterial: String, liquidType: String) { let reusable = reusable > 0 self.init(model: model, serialNumberInt: serialNumberInt, reusable: reusable, encasingMaterial: encasingMaterial, liquidType: liquidType) } |
Now use LiquidTank
‘s convenience initializer #3c to instantiate a new rocket propellant fuel tank:
let rp1Tank = LiquidTank(model: "Hermes", serialNumberInt: 5, reusable: 1, encasingMaterial: "Aluminum", liquidType: "LOX") |
This initialization follows the following flow through the funnel of initializers: 3c > 3b > 3a > 2a > 1a. It first goes across LiquidTank
‘s two convenience initializers; then delegation continues to LiquidTank
‘s designated initializer. From there, initialization is delegated up to all the superclasses, Tank
and RocketComponent
. This also takes advantage of the same design decision you learned in Part 1 of this tutorial: two initializers can use the same set of parameter names and the runtime is smart enough to figure out which one to use based on the type of each argument. In this case, the initializer is called with an integer for reusable
to indicate how many more times it can be launched, which means the compiler picks initializer #3c.
Re-inheriting Initializers
Remember that subclasses stop inheriting initializers once they define their own designated initializer. What if you want to be able to initialize a subclass using one of the superclass initializers?
Add the following designated initializer to Tank
:
// Init #2b - Designated override init(model: String, serialNumber: String, reusable: Bool) { self.encasingMaterial = "Aluminum" super.init(model: model, serialNumber: serialNumber, reusable: reusable) } |
This makes one of RocketComponent
’s initializers available for instantiating Tank
instances. When you want to use a superclass’s un-inherited designated initializer to instantiate a subclass, you override it just like in the example above.
Now add the following designated initializers to LiquidTank
:
// Init #3d - Designated override init(model: String, serialNumber: String, reusable: Bool) { self.liquidType = "LOX" super.init(model: model, serialNumber: serialNumber, reusable: reusable, encasingMaterial: "Aluminum") } // Init #3e - Designated override init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) { self.liquidType = "LOX" super.init(model: model, serialNumber: serialNumber, reusable: reusable, encasingMaterial: encasingMaterial) } |
This makes both RocketComponent
‘s and Tank
‘s designated initializers available for instantiating LiquidTank
instances.
Now that these designated initializers are back in service, uncomment the fuelTank
and liquidOxygenTank
instantiations from before:
let fuelTank = Tank(model: "Athena", serialNumber:"003", reusable: true) let liquidOxygenTank = Tank(identifier: "LOX-012", reusable: true) |
The first line instantiates a Tank
using the RocketComponent
‘s overridden designated initializer. But take a look at the second line: it’s instantiating a Tank
using RocketComponent
‘s convenience initializer! You didn’t override it. This is how you get subclasses to inherit their superclasses’ convenience initializers.
Override all of a superclass’s set of designated initializers in order to re-inherit the superclass’s set of convenience initializers.
To go one step further, instantiate a LiquidTank
using RocketComponent
‘s convenience initializer at the bottom of your playground:
let loxTank = LiquidTank(identifier: "LOX-1", reusable: true) |
This overrides all of Tank
‘s designated initializers. This means that LiquidTank
inherits all of RocketComponents
‘s convenience initializers.
Overriding Using Convenience Initializers
Designated initializers are supposed to be very basic assignments of values to all stored properties, and normally take in an initial value for each property. Any time you need an initializer that simplifies initialization by having fewer arguments and/or doing some pre-processing on property values, you should make that a convenience initializer. That’s why they’re called convenience initializers, after all: they make initialization more convenient than using a designated initializer.
In the previous section, you overrode RocketComponent
‘s designated initializer with a designated initializer in Tank
. Think about what this override is doing: it is overriding RocketComponent
‘s designated initializers to make it more convenient to initialize Tank
instances. Initialization is more convenient because this override does not require a value for encasingMaterial
.
When overriding a superclass designated initializer, you can either make it a designated initializer or a convenience initializer. To turn the overridden initializers in LiquidTank
into convenience initializers, replace the code for initializers #3d and #3e with:
// Init #3d - Convenience convenience override init(model: String, serialNumber: String, reusable: Bool) { self.init(model: model, serialNumber: serialNumber, reusable: reusable, encasingMaterial: "Aluminum", liquidType: "LOX") } // Init #3e - Convenience convenience override init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) { self.init(model: model, serialNumber: serialNumber, reusable: reusable, encasingMaterial: "Aluminum") } |
There is one downside to overriding superclass designated initializers with subclass designated initializers. If the subclass designated initializer has logic, you can’t delegate to it from a convenience initializer. Instead, you can override a superclass’s designated initializer with a convenience initializer; this allows you to delegate to the subclass’s designated initializer logic. You’ll try this next.
Failing and Throwing Strategy With Inheritance
Add the following convenience initializer to LiquidTank
:
// Init #3f - Convenience convenience init?(identifier: String, reusable: Bool, encasingMaterial: String, liquidType: String) { let identifierComponents = identifier.componentsSeparatedByString("-") guard identifierComponents.count == 2 else { return nil } self.init(model: identifierComponents[0], serialNumber: identifierComponents[1], reusable: reusable, encasingMaterial: encasingMaterial, liquidType: liquidType) } |
This initializer looks almost identical to RocketComponent
‘s convenience initializer.
Instantiate a new LiquidTank
using this new convenience initializer:
let athenaFuelTank = LiquidTank(identifier: "Athena-9", reusable: true, encasingMaterial: "Aluminum", liquidType: "RP-1") |
This works, but there’s duplicate code in LiquidTank
‘s and in RocketComponent
‘s initializers. Duplicate code introduces the possibility for bugs, so this just won’t do.

No one wants bugs on Mars.
To follow the DRY (Don’t Repeat Yourself) principle, add the following type method to RocketComponent
:
static func decomposeIdentifier(identifier: String) -> (model: String, serialNumber: String)? { let identifierComponents = identifier.componentsSeparatedByString("-") guard identifierComponents.count == 2 else { return nil } return (model: identifierComponents[0], serialNumber: identifierComponents[1]) } |
This code refactors out the duplicate code from the initializers and places it in a single place accessible to both initializers.
Note: Type methods are marked with the static
keyword. They are called on the type itself instead of an instance. You might not have thought of it this way before, but calling a method inside the implementation of a class is really calling an instance method. For example, self.doSomething()
. You need to use a type method in this situation because decomposeIdentifier(_:)
as an instance method would not be available until phase 2, after all properties have been initialized. You’re using decomposeIdentifier(_:)
as a utility to calculate a property value for initialization. I recommend playing around with calling methods in initializers of your own classes to get the hang of this concept.
Now update both convenience initializers from RocketComponent
and LiquidTank
to call the new static method:
// Init #1c - Convenience convenience init?(identifier: String, reusable: Bool) { guard let (model, serialNumber) = RocketComponent.decomposeIdentifier(identifier) else { return nil } self.init(model: model, serialNumber: serialNumber, reusable: reusable) } |
and:
// Init #3f - Convenience convenience init?(identifier: String, reusable: Bool, encasingMaterial: String, liquidType: String) { guard let (model, serialNumber) = RocketComponent.decomposeIdentifier(identifier) else { return nil } self.init(model: model, serialNumber: serialNumber, reusable: reusable, encasingMaterial: encasingMaterial, liquidType: liquidType) } |
This is a great way to take advantage of failable convenience initializers while removing any redundancy. You can still have the initializer return nil as needed, and you’ve refactored the common code into the type method to avoid repeating yourself.
Where to Go From Here?
Whew, that was a lot to cover! You’ve learned a ton about initialization in this two-parter tutorial … and thanks to you, the first manned mission to Mars is ready for lift-off. Great job!
If you want to compare code or just want a look at the final code, the complete playground for Part 2 can be downloaded here.
To review what you’ve learned here and to read about all the initialization rules and compiler safety checks, download Apple’s book The Swift Programming Language.
To get more practice and to see initialization in the wider context of structs and classes, check out our book, Swift Apprentice. We also have a great tutorial on Object-Oriented Design in Swift which touches on initialization.
Now that Swift is open source, you can find lots of interesting documents related to initialization in the Swift GitHub repo under the docs folder. By reading these documents you can learn the Swift team’s justification for initialization features and safety checks. Follow the Swift-evolution and Swift-evolution-announce mailing lists to get a glimpse of what’s on the horizon.
We hope you enjoyed this tutorial, and if you have any questions or comments, please join the forum discussion below!
The post Swift Tutorial: Initialization In Depth, Part 2/2 appeared first on Ray Wenderlich.