Swift Initializers

Swift Initializers

Apple docs: Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization that is required before the new instance is ready for use.

Initializers are similar to constructors in java programming. Swift being a type-safe language has placed a lot of rules for initializers. It can get tricky to implement unless you’ve got a good hold of the concept.

Classes and structures must set all of their stored properties to an appropriate initial value by the time an instance of that class or structure is created. Stored properties cannot be left in an indeterminate state.

Swift init() syntax

init() {
    
    // initialize the stored properties here.
    
}

Let’s look at a sample class below.

import Foundation

class TestClass {  //Error: Class 'testClass' has no initializers
    
    var a: Int
    var b: String
    var c: Int?
    let website = "balututorial.com"

}

The above class won’t compile. The swift compiler complains that the stored properties aren’t initialized. Stored Properties can’t be kept in an undetermined state.

This leaves us with two possible options:

  1. Assign a default property value in the property definition itself.
  2. Use an initializer, init() for initializing the properties.

Let’s look at each of the approaches one at a time.

import Foundation

class TestClass {
    
    var a: Int = 5
    var b: String = "Hello. How you're doing"
    var c: Int?
    let website = "balututorial"

}

Here we’ve set a default value for each of the stored properties, hence Swift provides us the default initializer implicitly. All the properties and functions can be accessed using the dot operator over an instance of the class once it’s initialized.

var obj = TestClass()
obj.a = 10
obj.c = 2

The second way is to initialize the stored properties using the init() method as shown below.

import Foundation

class TestClass {
    
    var a: Int = 5
    var b: String = "Hello. How you're doing"
    var c: Int?
    let website = "balututorial"
    
    init(a: Int, b: String) {
        self.a = a
        self.b = b
    }
}

var obj = TestClass(a: 5, b: "Hello World")
Note: Swift Optional is not a stored properties. Hence, they need not be initialized.

Stored properties are accessed inside the init() method using self property.

Noteself is used to refer to the current instance within its own instance methods.
The above initializer is the primary initializer of the class. It’s also known as the designated initializer(we’ll discuss this later).

Initializers lets us modify a constant property too.

import Foundation

class TestClass {
    
    var a: Int
    var b: String
    var c: Int?
    let website: String
    
    init(a: Int, b: String, website: String) {
        self.a = a
        self.b = b
        self.website = website
    }
}

var obj = TestClass(a: 5, b: "Hello World", website: "www.balututorial.com")

Memberwise Initializers for Structures

Structures being value types, don’t necessarily require an initializer defined. Structure types automatically receive a memberwise initializer unless you’ve defined custom initializer(s).

Following are the code snippets that describe the various ways to initialize a struct.

import Foundation

struct Rect1 {
    
    var length: Int
    var breadth: Int
}
var r1 = Rect1(length: 5, breadth: 10)


struct Rect2 {
    
    var length: Int = 5
    var breadth: Int = 10
    
}
var r2 = Rect2()
var r3 = Rect2(length: 10, breadth: 5)

Since we’ve assigned default values to the stored properties in the above snippet, we receive a default initializer without member initialization along with the memberwise initializer.

import Foundation

struct Rect {
    
    var length: Int
    var breadth: Int
    
    init(length: Int, breadth: Int) {
        self.length =  length + 10
        self.breadth = breadth + 10
    }
}
var r = Rect(length: 10, breadth: 5)

In the above case, we’ve defined our own custom initializer.

Using Parameters without External Name

When an external name is not needed for an initializer, underscore ‘_’ is used to indicate the same as shown below.

import Foundation

class TestClass {
    
    var a: Int
    var b: String
    var c: Int?
    let website = "Balututorial"
    
    init(_ a: Int, _ b: String) {
        self.a = a
        self.b = b
    }
}

var object = TestClass(5,"Hello World")


struct Rect {
    
    var length: Int
    var breadth : Int
    
    init(_ length: Int, _ breadth: Int) {
        self.length =  length + 10
        self.breadth = breadth + 10
    }
}
var r = Rect(10, 10)

Types of Swift Initializers

Initializers for classes can be broadly classified into the following types:

  1. Designated Initializers: This is the primary initializer of the class. It must fully initialize all properties introduced by its class before calling any superclass initializer. A class can have more than one designated initializer. Every class must have at least one designated initializer.
  2. Convenience Initializers: These are secondary, supporting initializers for a class. They must call a designated initializer of the same class. These are optional and can be used for a custom setup. They are written in the same style, but with the convenience modifier placed before the init keyword
import Foundation

class Student {
    
    var name: String
    var degree: String
    
    init(name : String, degree: String) {
        self.name = name
        self.degree = degree
    }
    
    convenience init() {
        self.init(name: "Unnamed", degree: "Computer Science")
    }
    
}
var student = Student()
student.degree // "Computer Science"
student.name // "Unnamed"

Convenience Initializers are useful when it comes to assigning default values to stored properties.

Swift Initializer Delegation For Structure

It’s possible to call an initializer from another one thereby avoiding code duplication. Value Types like Structures do not support inheritance. Hence the only possible way is to call initializer within the same structure. An example is given below.

import Foundation

struct Rect {
    var length: Int
    var breadth: Int
    
    init(_ length: Int, _ breadth: Int) {
        self.length =  length
        self.breadth = breadth
    }
    
    init(_ length: Int) {
        self.init(length, length)
    }
}
var r = Rect(10, 5)
var r1 = Rect(15) //initialises the length and breadth to 15

Swift Initializer Delegation For Classes

Classes being reference types support inheritance. Thus initializers can call other initializers from superclass too thereby adding responsibilities to properly inherit and initialize all values.
Following are the primary rules defined for handling relationships between initializers.

  • A designated initializer must call a designated initializer from its immediate superclass.
  • A convenience initializer must call another initializer from the same class.
  • A convenience initializer must ultimately call a designated initializer.

Following illustration describes the above rules.Swift initializer delegationSwift Doc Reference

Designated initializers must always delegate up. Convenience initializers must always delegate across. super keyword is not possible for a convenience initializer in a subclass.

Swift Initializer Inheritance and Overriding

Subclasses in Swift do not inherit their superclass’s initializers by default unless certain conditions are met(Automatic Initializer Inheritance). This is done to prevent half-baked initialization in the subclass.
Let’s look at how designated and convenience initializers work their way through inheritance.
We’ll be defining a Vehicle base class that’ll be inherited by the relevant subclasses. We’ll use Enum as a type in the classes.

Our base class Vehicle is defined as shown below.

import Foundation

enum VehicleType: String {
    
    case twoWheeler = "TwoWheeler"
    case fourWheeler = "FourWheeler"
    
}

class Vehicle {
    
    var vehicleType: VehicleType
    
    init(vehicleType: VehicleType) {
        self.vehicleType = vehicleType
        print("Class Vehicle. vehicleType is \(self.vehicleType.rawValue)\n")
    }
    
    convenience init() {
        self.init(vehicleType: .fourWheeler)
    }
}

var v = Vehicle(vehicleType: .twoWheeler)

Note: The convenience initializer must call the designated initializer of the same class using self.init

Let’s define a subclass of the above class as shown below.

enum TwoWheelerType: String {
    
    case scooty = "Scooty"
    case bike = "Bike"
    
}

class TwoWheeler: Vehicle {
    
    var twoWheelerType: TwoWheelerType
    var manufacturer: String
    
    init(twoWheelerType : TwoWheelerType, manufacturer : String, vType : VehicleType) {
        self.twoWheelerType = twoWheelerType
        self.manufacturer = manufacturer
        print("Class TwoWheeler. \(self.twoWheelerType.rawValue) manufacturer is \(self.manufacturer)")
        super.init(vehicleType: vType)
        
    }
}

Important points to note:

  • The designated initializer of the subclass must initialize its own properties before calling the designated initializer of the superclass.
  • A subclass can modify inherited properties of the superclass only after the super.init is called.

The following code would lead to a compile-time error.

class TwoWheeler: Vehicle {
    
    var twoWheelerType: TwoWheelerType
    var manufacturer: String
    init(twoWheelerType: TwoWheelerType, manufacturer: String, vType: VehicleType) {
        self.twoWheelerType = twoWheelerType
        self.manufacturer = manufacturer
        self.vehicleType = vType //Won't compile 'self' used in property access 'vehicleType' before 'super.init' call
        super.init(vehicleType: vType)
        //self.vehicleType = .fourWheeler //This would work.
    }
}
var t = TwoWheeler(twoWheelerType: .scooty, manufacturer: "Hero Honda", vType: .twoWheeler)

As explained earlier, the superclass initializer isn’t inherited automatically in the subclass. So the below initialization would fail.

var t = TwoWheeler(vehicleType: .twoWheeler)
/*
   - Error: Extra argument 'vehicleType' in call
   - manufacturer property isn't initialized
 */

To override an initializer, the subclass initializer must match with the designated initializer of the superclass. The override keyword is appended to the initializer in this case.

class TwoWheeler: Vehicle {
    
    var twoWheelerType: TwoWheelerType
    var manufacturer: String
    
    init(twoWheelerType : TwoWheelerType, manufacturer : String, vType : VehicleType) {
        self.twoWheelerType = twoWheelerType
        self.manufacturer = manufacturer
        print("Class TwoWheeler. \(self.twoWheelerType.rawValue) manufacturer is \(self.manufacturer)")
        super.init(vehicleType: vType)
    }
    
    override init(vehicleType: VehicleType) {
        print("Class TwoWheeler. Overriden Initializer. \(vehicleType.rawValue)")
        self.twoWheelerType = .bike
        self.manufacturer = "Not defined"
        super.init(vehicleType: vehicleType)
    }
}

The below initializer doesn’t override the one from superclass since the parameter name is different.

override init(v: VehicleType) {
        self.twoWheelerType = .bike
        self.manufacturer = "Not defined"
        super.init(vehicleType: v)
    }
    //Error: Argument labels for initializer 'init(v:)' do not match those of overridden initializer 'init(vehicleType:)'

Using Convenience Initializer to override the one from superclass.

class TwoWheeler: Vehicle {
    var twoWheelerType: TwoWheelerType
    var manufacturer: String
    init(twoWheelerType : TwoWheelerType, manufacturer : String, vType : VehicleType) {
        self.twoWheelerType = twoWheelerType
        self.manufacturer = manufacturer
        print("Class TwoWheeler. \(self.twoWheelerType.rawValue) manufacturer is \(self.manufacturer)")
        super.init(vehicleType: vType)
    }
    
    override convenience init(vehicleType: VehicleType) {
        self.init(twoWheelerType: .bike, manufacturer: "Not Defined", vType: .twoWheeler)
        self.vehicleType = vehicleType
    }
}
var t = TwoWheeler(twoWheelerType: .scooty, manufacturer: "Hero Honda", vType: .twoWheeler)
t = TwoWheeler(vehicleType: .twoWheeler)

/* Output
    Class Vehicle. vehicleType is TwoWheeler

    Class TwoWheeler. Scooty manufacturer is Hero Honda
    Class Vehicle. vehicleType is TwoWheeler

    Class TwoWheeler. Bike manufacturer is Not Defined
    Class Vehicle. vehicleType is TwoWheeler
 */

The convenience initializer has override keyword appended to it. It calls the designated initializer of the same class.

Note: The order of the keywords convenience and override doesn’t matter.

Required Initializers

Writing the keyword required before the initializer indicates that each subclass must implement that initializer. Also, the required modifier must be present at the respective subclass implementations as well. An example of Required Initializers on the above two classes is given below.

import Foundation

enum VehicleType: String {
    
    case twoWheeler = "TwoWheeler"
    case fourWheeler = "FourWheeler"
    
}

enum TwoWheelerType: String {
    
    case scooty = "Scooty"
    case bike = "Bike"
    
}

class Vehicle {
    
    var vehicleType: VehicleType
    
    required init(vehicleType: VehicleType) {
        self.vehicleType = vehicleType
        print("Class Vehicle. vehicleType is \(self.vehicleType.rawValue)\n")
    }
    
    convenience init() {
        self.init(vehicleType: .fourWheeler)
    }
}

class TwoWheeler: Vehicle {
    
    var twoWheelerType: TwoWheelerType
    var manufacturer: String
    
    init(twoWheelerType : TwoWheelerType, manufacturer : String, vType : VehicleType) {
        self.twoWheelerType = twoWheelerType
        self.manufacturer = manufacturer
        print("Class TwoWheeler. \(self.twoWheelerType.rawValue) manufacturer is \(self.manufacturer)")
        super.init(vehicleType: vType)
    }
    
    required init(vehicleType: VehicleType) {
        self.manufacturer = "Not Defined"
        self.twoWheelerType = .bike
        super.init(vehicleType: vehicleType)
    }
}
Note: Adding a required modifier, indicates that the initializer would be overridden. Hence the override keyword can be ommitted in the above case.

Using a Required Initializer with Convenience

Required and convenience initializers are independent of each other and can be used together. Let’s create another subclass of Vehicle to demonstrate the use of required and convenience modifiers together.

enum FourWheelerType: String {
    case car = "Car"
    case bus = "Bus"
    case truck = "Truck"
}


class FourWheeler: Vehicle {
    
    var fourWheelerType: FourWheelerType
    var name: String
    
    init(fourWheelerType : FourWheelerType, name: String, vehicleType: VehicleType) {
        self.fourWheelerType = fourWheelerType
        self.name = name
        print("Class FourWheeler. \(self.fourWheelerType.rawValue) Model is \(self.name)")
        super.init(vehicleType: vehicleType)
        self.vehicleType = vehicleType
    }
    
    required convenience init(vehicleType: VehicleType) {
        self.init(fourWheelerType: .bus, name: "Mercedes", vehicleType: vehicleType)
    }
}


class Car: FourWheeler{
    
    var model : String
    
    init(model: String) {
        self.model = model
        print("Class Car. Model is \(self.model)")
        super.init(fourWheelerType: .car, name: self.model, vehicleType: .fourWheeler)
    }
    
    required init(vehicleType: VehicleType) {
        self.model = "Not defined"
        print("Class Car. Model is \(self.model)")
        super.init(fourWheelerType: .car, name: self.model, vehicleType: vehicleType)
    }
    
}

Important things to note in the above code snippet:

  • Convenience initializers are secondary initializers in a class.
  • Setting a convenience initializer as required means that implementing it in the subclass is compulsory.

Automatic Initializer Inheritance

There are two circumstances under which a subclass automatically inherits the initializers from the superclass.

  • Don’t define any designated initializers in your subclass.
  • Implement all the designated initializers of the superclass. All the convenience initializers would be automatically inherited too.

The first rule in action is demonstrated in the snippet below:

import Foundation

class Name {
    
    var name: String
    
    init(n: String) {
        self.name = n
    }
}

class Tutorial: Name {
    var tutorial: String? = "Swift Initialization"
}

var parentObject = Name(n: "Balu Naik")
var childObject = Tutorial(n: "balututorial")

The second rule in action is demonstrated in the snippet below.

import Foundation

class Name {
    
    var name: String
    
    init(n: String) {
        self.name = n
    }
    
    convenience init() {
        self.init(n: "No name assigned")
    }
}

class Tutorial: Name {
    
    var tutorial: String? = "Swift Tutorial"
    
    override init(n : String) {
        super.init(n: n)
    }
}

var parentObject = Name(n: "Balu Naik")
var childObject = Tutorial(n: "balututorial")
var childObject2 = Tutorial()
print(childObject2.name) //prints "No name assigned

The convenience initializer of the superclass is automatically available in the subclass in the above code.

Swift Failable Initializer

We can define a failable initializer using the keyword init? on Classes, Structures or Enumerations which gets triggered when the initialization process fails.
Initialization can fail for various reasons: Invalid parameter values, absence of an external source etc. A failable initializer creates an optional value of the type it initializes. We’ll be returning a nil to trigger an initialization failure(Though an init doesn’t return anything).

Failable Initializer with Structure

import Foundation

struct SName {
    
    let name: String
    
    init?(name: String) {
        if name.isEmpty {
            
            return nil
        }
        self.name = name
    }
}

var name = SName(name: "balututorial")
if name != nil {
    print("init success") //this gets displayed
} else {
    print("init failed")
}
name  = SName(name: "")

if name != nil {
    print("init success")
} else {
    print("init failed") //this gets displayed
}

Failable Initializers With Enums

import Foundation

enum CharacterExists {
    
    case A, B
    
    init?(symbol: Character) {
        switch symbol {
        case "A":
            self = .A
        case "B":
            self = .B
        default:
            return nil
        }
    }
}


let ch = CharacterExists(symbol: "C")
if ch != nil {
    print("Init failed. Character doesn't exist")
}

class CName {
    
    let name: String
    
    init?(name: String) {
        if name.isEmpty {
            
            return nil
        }
        self.name = name
    }
}
var name  = CName(name: "")

if name != nil {
    print("init success")
} else {
    print("init failed")
}
Note: A failable initializer and a non-failable initializer can’t have the same parameter types and names.

Overriding a Failable Initializer

You can override a failable initializer in your subclass. A failable initializer can be overridden with a non-failable initializer but it cannot happen vice-versa. An example of overriding a failable with a non-failable initializer is given below.

import Foundation

class CName {
    let name: String
    
    init?(name: String) {
        if name.isEmpty {
            
            return nil
        }
        self.name = name
    }
}
var name = CName(name: "")

class SubName: CName{
    
    var age : Int
    override init(name: String) {
        self.age = 23
        super.init(name: name)!
    }
}
Forced unwrapping is used to call a failable initializer from the superclass as part of the implementation of a subclass’s nonfailable initializer.

whatsapp

error: Content is protected !!