Swift Programming – Initialization

In this tutorial you’ll learn about initialization in Swift. To follow along, it is recommended to have a Mac and the Xcode IDE installed on it. This tutorial makes use of the Xcode playground for compilation of the Swift codes. If you do not have a Mac, you may try using the open-source code editors which support different operating systems such as AtomSublimeText or VSCode.

Initialization

Initialization is when we create an instance of a class, structure or enumeration. We can set values to stored properties during the initialization. Recall in our lesson Swift Programming – Structures and Classes, we created instances of structures and classes. Unlike classes, structures provided us with memberwise initializers. Classes on the other hand, need an explicit initializer with parameters to provide values for the stored properties. Let’s try examples of both for a better understanding.

// structure person
struct SPerson {
    var name: String
    var address: String
    var email: String
    var phone: Int
}

// class person
class CPerson {
    var name: String
    var address: String
    var email: String
    var phone: Int
    
    // initializer
    init(name: String, address: String, email: String, phone: Int) {
        self.name = name
        self.address = address
        self.email = email
        self.phone = phone
    }
}

let sPerson = SPerson(name: "Grace", address: "Middleton", email: "grace@mail.com", phone: 32458799)
let cPerson = CPerson(name: "Hannah", address: "Middleton", email: "hannah@mail.com", phone: 99785423)

In the example above, we defined a structure and a class with the same stored properties. For the structure SPerson, we did not provide an initializer and we were still able to initialize the stored properties. For the class CPerson, we had to provide an initializer to be able to initialize the stored properties.

Setting Initial Values for Stored Properties

All stored properties in a structure or a class, must be initialized when defining an instance of it. This means that creating an instance of a structure or a class will work only if you provided default values to stored properties or initialized them through an initializer.

Customizing Initialization

So far we created a default initialization for our class where we are accepting parameters to initialize our stored properties. We can provide custom initializations as well. This means that we can, for example, provide multiple initializations with different argument labels or provide some parameters for some of the stored properties and initialize the rest with default values in the implementation code of our initializer. This applies to both structures and classes.

struct ConversionToGram {
    var output: Double
    
    // with different argument label and parameter name
    init(withKg value: Double) {
        output = value * 1000.0
    }
    
    // with same argument label and parameter name
    init(mgValue: Double) {
        output = mgValue / 1000.0
    }
    
    // without argument label
    init(_ value: Double) {
        output = value
    }
}

let kgToGram = ConversionToGram(withKg: 15)
let mgToGram = ConversionToGram(mgValue: 2500)

print("15kg = \(kgToGram.output)g and 2500mg = \(mgToGram.output)g")

Optional Property Types

If you define an optional property type to your class or structure, you can omit it from your initializer’s parameters.

class Quiz {
    var question: String
    var answer: String?
    
    init(question: String) {
        self.question = question
    }
}

let quiz = Quiz(question: "What is the capital of Australia?")
quiz.answer = "Canberra"

// '\n' is for outputting on another line
print("\(quiz.question)\n\(quiz.answer ?? "no value found")")

Class Inheritance and Initialization

A class inheriting from another, must initialize all the stored properties of its superclass when initializing. Swift provides two kinds of initializers to ensure that all stored properties are properly initialized. These are the designated initializers and convenience initializers.

Designated Initializers and Convenience Initializers

A designated initializer is the primary initializer which initializes all the stored properties of a class and its superclass. A class must have at least one designated initializer.

A convenience initializer is the secondary initializer which supports initializers in a class. It must call a designated initializer in its implementation and provide default values to the parameters. It is not vital to have a convenience initializer in your class if your class do not need it. Consider it as a shortcut to your initialization process for a clearer intent or a quick initialization. Write the keyword convenience before the init() when defining a convenience initializer.

Initializer Inheritance and Overriding

You can define a designated initializer similar to the one of a class’s superclass provided that you write the override modifier prior to init() and you call the super.init() in your init()‘s implementation code to include the superclass’ designated initializer. If you are going to specify values to all the stored properties of the subclass, therefore you can omit the super.init() call, as Swift will implicitly call it.

enum Brand {
    case Apple, Dell, Lenovo, Asus, HP, Acer, MSI
}

class Computer {
    var size: Double
    var brand: Brand
    
    init(size: Double, brand: Brand) {
        self.size = size
        self.brand = brand
    }
}

class Mac: Computer {
    let hasQuickPreview = true
    let hasMultiTouchGesture = true
    
    init(size: Double) {
        super.init(size: size, brand: .Apple)
    }
}

let macBookPro = Mac(size: 16)
print("MacBook Pro with resolution \(macBookPro.size)-inch, of brand \(macBookPro.brand)")

Automatic Initializer Inheritance

A class inheriting another class, does not inherit its superclass initializers by default. However, if a subclass does not provide any designated initializer, it inherits its superclass initializers automatically. Also, if the subclass implements all the designated initializers of its superclass, then it inherits its superclass convenience initializers automatically.

class Item {
    var title: String
    
    init(title: String) {
        self.title = title
    }
}

class Task: Item {
    var completed = false
}

let task = Task(title: "Laundry")
task.completed = true

print("\(task.title), completed \(task.completed)")

Failable Initializers

A failable initializer can be used for anticipating where an initialization might fail. You can define a failable initializer in a class, structure or enumeration.

class Grocery {
    var item: String
    
    init?(item: String) {
        if item.isEmpty {
            return nil
        }
        
        self.item = item
    }
}

let grocery = Grocery(item: "")

if let object = grocery {
    print("object is \(object.item)")
} else {
    print("grocery is empty")
}

Required Initializers

If your class needs its subclasses to implement its initializer, then you can write the required modifier before the definition of the initializer.

class MyClass {
    required init() {
        
    }
}

Setting a Default Property Value with a Closure or Function

You can set a default property value with a closure or global function, if your stored property require some sort of computation to obtain that value.

class Multiple {
    var five: [String: Int] = {
        var output = [String: Int]()
        
        for i in 1...12 {
            let multiplication = i * 5
            output["\(i) x 5"] = multiplication
        }
        
        return output
    }()
    
    var six: [String: Int] = {
        var output = [String: Int]()
        
        for i in 1...12 {
            let multiplication = i * 6
            output["\(i) x 6"] = multiplication
        }
        
        return output
    }()
}

let multiple = Multiple()

print(multiple.five)
print(multiple.six)

Reference

https://docs.swift.org/swift-book/LanguageGuide/Initialization.html