Two-Phase Initialization in Swift

One then another

Image for post
Image for post
Photo by Vincent van Zalinge on Unsplash

I’ve previously written about Swift initializers, however I didn’t EVEN MENTION Two-Phase Initilization.

Time to put that right!

Difficulty: Beginner | Easy | Normal | Challenging
This article has been developed using Xcode 11.5, and Swift 5.2.4

Prerequisites:

What is Swift’s Two-Phase Initialization

Swift has many safety features that allow us to have a great experience programming.

So what has Swift done to ensure that properties are set before a class (or indeed a struct) is ready for use?

The answer is the aforementioned Two-Phase Initialization: Two-phase initialization prevents property values from being accessed before they are are initialized, and prevent the property values from being set to a different value by another initializer unexpectedly.

The safety checks

All properties introduced by it’s class are initalized before it delegates up to a superclass initializer.

The memory for an object is only fully initialized once the initial state of each of it’s store properties are known. To satisfy this rule, a designated initializer makes sure that it’s own perperties are intialized before it hands back up the chain.

A designated initilizer must delegate up to the superclass initializer before assigning a value to an inherited property. The alternative is to assign the new value to overwite the superclass as part of it’s own initialization.

Convenience initializers delegate up to a superclass initializer before assigning a value to an inherited property. The alternative is to assign the new value from a convenience initializer by the class’s own designated initializer.

An initializer can’t call any instant methods, read the value of any instance properties or return to self until the first phase of initialization is complete.

A derived class

Each initilizer must initialize all of it’s properties, then call it’s superclass in order to do the same.

The phases

  • When a designated or convenience initializer is called on a class memory for the instance is allocated, but not initialized
  • Designated initializers confirm that stored properties introduced by that class have a value, meaning that the memory for the stored values is initialized
  • The designated initializer hands off to the superclass initializer to recursively initialize the properties for it’s own stored properties — and this is continued until the top of the chain is reached
  • At the top of the chain, the stored properties have a value. Your instance has initialized memory, and phase 1 is complete
  • Working from the top of the chain, each designated initializer in the chain can customize the instance further. That is, initializers can access self and can modify properties using self to do so an call instance methods.
  • Convenience initializers in the chain can customize the instance and work with self

Examples

Let us look at the following example.

Image for post
Image for post

When you call Witch() the first section of the initializer calls super.init(), which initializes the superclass (Mage). Since the Witch() properties are initialized at the super call (as represented by spellPower = 4).

Once that is complete, phase 2 can kick off. After the super.init() call additional customizations can be performced, represented by physicalPower = 2 and charm = 3

NOTE: The properties are optionals and The derived object example covers the example where they are not, later in this article

To perform calculations you need to initialize all properties first

The following will not compile with the following error message:

'self' captured by a closure before all members were initialized

Image for post
Image for post

and of course if we initiaize all of the members first we have no such problem:

Image for post
Image for post

If you don’t use the && operation, and take care not to try to use a property before it is initialized then you also have no such issues

Image for post
Image for post

The derived object

The subclass here (Dog) is known to inherit from it's superclass (Animal) before the object is created. If any methods were overriden, the superclass will use the overriden methods which can use the subclass's member variables.

Since all subclass variables must be initialized before superclass method the correct order is

self.owner = owner super.init(name: name)

In Swift, when you create a Derived object, it is a Derived object from the start. If methods are overridden, then the Base init method will already use the overridden methods, which might access Derived member variables. Therefore all Derived member variables must be initialised before the Base init method is called.

Image for post
Image for post

Conclusion

Two Phase initialization is one of those concepts in Swift that is ideal to learn as it will help the speed of writing great software, while at the same time will help stop you relying on the compiler for these things.

Wouldn’t that be great? If you want the full playground download it! You’re welcome…!

If you’ve any questions, comments or suggestions please hit me up on Twitter

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store