Swift Tutorial: Initialization In Depth, Part 1/2

Take your Swift skills to the next level by learning about how your instances are initialized in this in-depth two-part tutorial on initialization! By René Cacheaux.

Leave a rating/review
Save for later
Share

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

Some things are inherently awesome: rockets, missions to Mars, initialization in Swift. This tutorial combines all three for a combo-platter of awesomeness in which you’ll get to learn about the power of initialization!

Initialization in Swift is about what happens when you create a new instance of a named type:

let number = Float()

Initialization is the time to manage the inital values of stored properties for named types: classes, structures, and enumerations. Because of the safety features built into Swift, initialization can be tricky. There are a lot of rules, some of which are not obvious.

By following this two-part tutorial, you will learn the ins and outs to designing initializers for your Swift types. In Part 1, you’ll begin with the basics including structure initialization, and in Part 2 you’ll move on to learning about class initialization.

Before getting started, you should be familiar with the basics of initialization in Swift and be comfortable with concepts such as optional types, throwing and handling errors, and declaring default stored property values. Also, make sure you have Xcode 8.0 or later installed.

If you need a refresher on the basics, or if you are just starting to learn Swift, check out our book Swift Apprentice or our many Swift intro tutorials.

Getting Started

Let’s set the scene: it’s your first day on your new job as a launch software engineer at NASA (go you!). You’ve been tasked with designing the data model that will drive the launch sequence for the first manned mission to Mars, Mars Unum. Of course, the first thing you do is convince the team to use Swift. Then …

Open Xcode and create a new playground named BlastOff. You can select any platform, since the code in this tutorial is platform-agnostic and depends only on Foundation.

Throughout the tutorial, remember this one golden rule: You cannot use an instance until it is fully initialized. “Use” of an instance includes accessing properties, setting properties and calling methods. Everything in Part 1 applies specifically to structures unless otherwise specified.

Banking on the Default Initializer

To start modeling the launch sequence, declare a new structure named RocketConfiguration in your playground:

struct RocketConfiguration {

}

Below the closing curly brace of the definition for RocketConfiguration, initialize a constant instance named athena9Heavy:

let athena9Heavy = RocketConfiguration()

This uses a default initializer to instantiate athena9Heavy. In the default initializer, the name of the type is followed by empty parentheses. You can use default initializers when your types either don’t have any stored properties, or all of the type’s stored properties have default values. This holds true for both structures and classes.

Add the following three stored properties inside the struct definition:

let name: String = "Athena 9 Heavy"
let numberOfFirstStageCores: Int = 3
let numberOfSecondStageCores: Int = 1

Notice how the default initializer still works. The code continues to run because all the stored properties have default values. That means the default initializer doesn’t have very much work to do since you’ve provided defaults!

What about optional types? Add a variable stored property named numberOfStageReuseLandingLegs to the struct definition:

var numberOfStageReuseLandingLegs: Int?

In our NASA scenario, some of the rockets are reusable, while others are not. That’s why numberOfStageReuseLandingLegs is an optional Int. The default initializer continues to run fine because optional stored property variables are initialized to nil by default. However, that’s not the case with constants.

Change numberOfStageReuseLandingLegs from a variable to a constant:

let numberOfStageReuseLandingLegs: Int?

Notice how the playground reports a compiler error:

You won’t run into this often, since constant optionals are rarely needed. To fix the compiler error, assign a default value of nil to numberOfStageReuseLandingLegs:

let numberOfStageReuseLandingLegs: Int? = nil

Hooray! The compiler is happy again, and initialization succeeds. With this setup, numberOfStageReuseLandingLegs will never have a non-nil value. You cannot change it after initialization, since it is declared as a constant.

Banking on the Memberwise Initializer

Rockets are usually made up of several stages, which is the next thing to model. Declare a new struct named RocketStageConfiguration at the bottom of the playground:

struct RocketStageConfiguration {
  let propellantMass: Double
  let liquidOxygenMass: Double
  let nominalBurnTime: Int
}

This time, you have three stored properties propellantMass, liquidOxygenMass and nominalBurnTime with no default values.

Create an instance of RocketStageConfiguration for the rocket’s first stage:

let stageOneConfiguration = RocketStageConfiguration(propellantMass: 119.1,
  liquidOxygenMass: 276.0, nominalBurnTime: 180)

None of RocketStageConfiguration‘s stored properties have default values. Also, there is no initializer implemented for RocketStageConfiguration. Why isn’t there a compiler error? Swift structures (and only structures) automatically generate a memberwise initializer. This means you get a ready-made initializer for all the stored properties that don’t have default values. This is super handy, but there are several gotchas.

Imagine you submit this snippet for code review and your developer team lead tells you all properties should be ordered alphabetically.

Update the RocketStageConfiguration to re-order the stored properties:

struct RocketStageConfiguration {
  let liquidOxygenMass: Double
  let nominalBurnTime: Int
  let propellantMass: Double
}

What happened? The stageOneConfiguaration initializer call is no longer valid, because the automatic memberwise initializer argument list’s order mirrors that of the stored property list. Be careful, because when re-ordering structure properties, you might break instance initialization. Thankfully the compiler should catch the error, but it is definitely something to watch out for.

Undo the stored property re-order change to get the playground compiling and running again:

struct RocketStageConfiguration {
  let propellantMass: Double
  let liquidOxygenMass: Double
  let nominalBurnTime: Int
}

All your rockets burn for 180 seconds, so it’s not useful to pass the nominal burn time every time you instantiate a stage configuration. Set nominalBurnTime‘s default property value to 180:

let nominalBurnTime: Int = 180

Now there’s another compiler error:

Compilation fails because memberwise initializers only provide parameters for stored properties without default values. In this case, the memberwise initializer only takes in propellant mass and liquid oxygen mass, since there is already a default value for burn time.

Remove nominalBurnTime‘s default value so that there is no compiler error.

let nominalBurnTime: Int

Next, add a custom initializer to the struct definition that provides a default value for burn time:

init(propellantMass: Double, liquidOxygenMass: Double) {
  self.propellantMass = propellantMass
  self.liquidOxygenMass = liquidOxygenMass
  self.nominalBurnTime = 180
}

Notice that the same compiler error is back on stageOneConfiguration!

Wait, shouldn’t this work? All you did was provide an alternative initializer, but the original stageOneConfiguration initialization should work because it’s using the automatic memberwise initializer. This is where it gets tricky: you only get a memberwise initializer if a structure does not define any initializers. As soon as you define an initializer, you lose the automatic memberwise initializer.

In other words, Swift will help you out to start. But as soon as you add your own initializer, it assumes you want it to get out of the way.

Remove the nominalBurnTime argument from stageOneConfiguration‘s initialization:

let stageOneConfiguration = RocketStageConfiguration(propellantMass: 119.1,
  liquidOxygenMass: 276.0)

All is good again! :]

But what if you still need the automatic memberwise initializer? You can certainly write the equivalent initializer, but that’s a lot of work. Instead, move the custom initializer into an extension before you instantiate an instance.

Your struct will now be in two parts: the main definition, and an extension with your two-parameter initializer:

struct RocketStageConfiguration {
  let propellantMass: Double
  let liquidOxygenMass: Double
  let nominalBurnTime: Int
}

extension RocketStageConfiguration {
  init(propellantMass: Double, liquidOxygenMass: Double) {
    self.propellantMass = propellantMass
    self.liquidOxygenMass = liquidOxygenMass
    self.nominalBurnTime = 180
  }
}

Notice how stageOneConfiguration continues to initialize successfully with two parameters. Now re-add the nominalBurnTime parameter to stageOneConfiguration‘s initialization:

let stageOneConfiguration = RocketStageConfiguration(propellantMass: 119.1,
  liquidOxygenMass: 276.0, nominalBurnTime: 180)

That works too! If the main struct definition doesn’t include any initializers, Swift will still automatically generate the default memberwise initializer. Then you can add your custom ones via extensions to get the best of both worlds.