Constructors, also known as initializer methods, are familiar to you. Whether you’re dealing with a class or struct, you can’t escape the initialization step. But while looking back at the Swift documentation recently, I noticed details that ☝️ hadn’t noticed before.

class ClassA {
    let name: String
    init(name: String) {
        self.name = name
    }
}

class ClassB: ClassA {
    let age: Int
    init(name: String, age: Int) {
        super.init(name: name)
        self.age = age
    }
}

Copy the code

When we write the initialization method of the subclass, we need to call the initialization method of the parent class in it. The above initialization method, in fact, will report an error, and the error is:

Property 'self.age' not initialized at super.init call

This error message is very simple, the attributes of the subclass must be initialized before the parent class’s initialization method call, or, more bluntly, self.age = age before super.init(name: name). But have you ever wondered why? What is the design logic behind this rule?

The first thing many people associate with objective-C constructors

(instancetype)init { self = [super init]; if (self ! = nil) { self.name = @"Jack"; } return self; }Copy the code

Obviously, objective-C initializers invariably call the constructor of the parent class and then initialize the attributes of the child class, which is intuitive to the developer. So why not Swift? Is it because of Swift’s unique mechanism?

Let’s see what Swift’s official documentation says. Class initialization in Swift is a two-stage process. In this case, the compiler performs four useful security checks to ensure that the constructor completes without errors. The first rule is:

A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.

The specified initializer must ensure that all attributes introduced by its class are initialized before being delegated to the superclass initializer.

As for the explanation of the first rule, although it is not explicitly written after the rule, Apple is very clear about the ultimate purpose of the two stages and four security checks:

The use of a two-phase initialization process makes initialization safe, while still giving complete flexibility to each class in a class hierarchy. Two-phase initialization prevents property values from being accessed before they are initialized, and prevents property values from being set to a different value by another initializer unexpectedly.

The use of a two-phase initialization process makes initialization safe while still providing complete flexibility for each class in the class hierarchy. Two-phase initialization prevents access to property values before they are initialized and prevents other initializers from accidentally setting property values to other values.

This explanation seemed a bit abstract until I came across an example from a blogger.

class ClassA {
    let name: String
    init(name: String) {
        self.name = name
        description()
    }

    func description(a) {
        print("I've initialized it. My name is:\(name)")}}class ClassB: ClassA {
    let age: Int
    init(name: String, age: Int) {
        self.age = age
        super.init(name: name)
    }

    override func description(a) {
        print("I've initialized it. My name is:\(name)My age is:\(age)")}}Copy the code

If you think about it, does the order in which the initialization of the subclass attributes and the initialization methods of the parent class really make no difference? What if we first initialize the parent constructor and then assign age?

Obviously, the program will not run because the age property has not yet been initialized in the description method that the parent class calls the subclass. Note that because age is decorated with the let keyword, its attributes must be initialized in the constructor. The value of age is not accessible until the constructor initializes the age attribute. Unreachable means not that age is nil, but that it has not been initialized, that memory has not been allocated. This is different from Objective-C, where a property is declared with an initial value nil, so the value of the property (nil) can be accessed even if it is not initialized in the constructor. This is also covered in the Swift documentation.

Swift’s two-phase initialization process is similar to initialization in objective-c. The main difference is that during phase 1, Objective-c assigns zero or null values (such as 0 or nil) to every property. Swift’s initialization flow is collection flexible in that it lets you set custom initial values, and can cope with types for which 0 or nil is not a valid default value.

Swift’s two-phase initialization process is similar to initialization in Objective-C. The main difference is that in phase 1, Objective-C assigns zero or null values (such as 0 or nil) to each attribute. Swift’s initialization process is more flexible because it lets you set custom initialization values and can handle types of invalid values that are valid 0 or nil.

Just because Swift’s constructor is more flexible (allowing immutable properties to be initialized in the constructor) and more secure (properties need to be accessed after initialization), it’s understandable that Apple would place additional requirements on Swift’s constructor.