Swift Tutorial: Initialization In Depth, Part 2/2

René Cacheaux

Update 11/4/16: This tutorial has been updated for Xcode 8 and Swift 3.

initialization-feature

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.

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.components(separatedBy: "-")
  guard identifierComponents.count == 2 else {
    return nil
  }
  self.reusable = reusable
  self.model = identifierComponents[0]
  self.serialNumber = identifierComponents[1]
}

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.components(separatedBy: "-")
  guard identifierComponents.count == 2 else {
    return nil
  }
  self.init(model: identifierComponents[0], serialNumber: identifierComponents[1],
    reusable: reusable)
}

This version is more concise.

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:

  1. Add a designated initializer that calls or overrides the designated initializer for the superclass RocketComponent.
  2. Add a convenience initializer that calls the designated initializer for the superclass RocketComponent.
  3. 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:

  1. Initialize the class’s own properties. In this case, that’s just encasingMaterial.
  2. 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.

ClassInitDelegationUp

Designated initializers in subclasses must delegate up to a designated initializer from the immediate superclass.

ClassInitDelegationAcross

Keep in mind that a designated initializer in a subclass cannot delegate up to a superclass convenience initializer.

ConvenienceToConvenienceCannot

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.

DesignatedToConvenienceCannot

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:

ExampleClassHierarchy

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.components(separatedBy: "-")
  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.

No one wants bugs on Mars.

To follow the DRY (Don’t Repeat Yourself) principle, add the following type method to RocketComponent:

static func decompose(identifier: String) ->
    (model: String, serialNumber: String)? {
  let identifierComponents = identifier.components(separatedBy: "-")
  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 decompose(identifier:) as an instance method would not be available until phase 2, after all properties have been initialized. You’re using decompose(identifier:) 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.decompose(identifier: 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.decompose(identifier: 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!

René Cacheaux

René, @rcachATX, is an iOS Client Architect at Atlassian, where he is known for his expertise in user interface engineering with UIKit, Core Animation, and Core Graphics. He has been engineering iOS apps since 2009.

When he's not coding, René loves to snow ski, ocean kayak, and root for the Texas Longhorns.

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

Swift Team

... 15 total!

iOS Team

... 44 total!

Android Team

... 15 total!

macOS Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 15 total!

Resident Authors Team

... 17 total!

Podcast Team

... 8 total!

Recruitment Team

... 9 total!