Filed Under: Swift

In this Swift tutorial, we’ll be discussing an important concept, namely Swift init or Swift initialization. Initialization is what happens when we create an instance of some type.

Swift init()

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.

Swift init() syntax


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

Let’s look at a sample class below.


class A{
    
    //Compilation error. No initializer is defined.
    var a : Int
    var b : String
    var c : Int?
    let website = "JournalDev"
}

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.


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

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 object = A()
object.a = 10
object.c = 2

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


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

var object = A(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.

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

Initializers lets us modify a constant property too.


class A{
    
    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 object = A(a: 5,b: "Hello World", website: "JournalDev")

Memberwise Initializers for Structures

Structures being value types, don’t neccessarily 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.


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

struct Rect{
    var length : Int = 5
    var breadth : Int = 10
}

var r = Rect()
var r1 = Rect(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 alongwith the memberwise initializer.


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.


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

var object = A(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

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 Value Types

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.


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 Reference Types

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 init, swift initialization delegation flow

Source: Apple Docs

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 Enums as a type in the classes.

Our base class Vehicle is defined as shown below.


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.

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
        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) //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.


//This would give a compile-time error since the parameter v doesn't match with the superclass.
override init(v: VehicleType)
    {
        self.twoWheelerType = .bike
        self.manufacturer = "Not defined"
        super.init(vehicleType: v)
    }

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
Following gets printed on the console:
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 initalizer 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.


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:


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

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

var parentObject = Name(n: "Anupam")
var childObject = Tutorial(n: "JournalDev")

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


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: "Anupam")
var childObject = Tutorial(n: "JournalDev")
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 Initializers With Structures


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

var name = SName(name: "JournalDev")
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


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.


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)!  
    }
}

Note: Forced unwrapping is used to call a failable initializer from the superclass as part of the implementation of a subclass’s nonfailable initializer.

This brings an end to swift initi tutorial.
References : Apple Docs

Comments

  1. sagar says:

    Overriding a Failable Initializer
    This topic I don’t understand one thing that, from subclass overridden init method super.init(name: name)! will crash if it returns nil right.?

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages