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:
- Assign a default property value in the property definition itself.
- 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")
[quote font=”helvetica”]Note: Swift Optional is not a stored properties. Hence, they need not be initialized.[/quote]
Stored properties are accessed inside the init() method using self property.
[quote font=”helvetica”]Note: self 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).[/quote]
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:
- 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.
- 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 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.
[quote font=”helvetica”]Note: The order of the keywords convenience and override doesn’t matter.[/quote]
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) } }
[quote font=”helvetica”]Note: Adding a required modifier, indicates that the initializer would be overridden. Hence the override keyword can be ommitted in the above case.[/quote]
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") }
[quote font=”helvetica”]Note: A failable initializer and a non-failable initializer can’t have the same parameter types and names.[/quote]
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)! } }
[quote font=”helvetica”]Forced unwrapping is used to call a failable initializer from the superclass as part of the implementation of a subclass’s nonfailable initializer.[/quote]