Initializers in swift part-1 : (intro, convenience and designated intializers)

Initialization is a huge topic to cover in swift. I will try to simplify it as much as I can in this article.

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.

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.

Initializers

In its simplest form, an initializer is like an instance method with no parameters, written using the init keyword:

init() {// perform some initialization here}

As I have mentioned before, Stored properties cannot be left in an indeterminate state. Some values should be assigned to them at the time of initilization. Consider the following class:

enum Gender {case male
case female
case unknown
}struct Human {var gender:Gender // this is a stored property.init(gender:Gender) {self.gender = gender}}let human = Human(gender: .male)

Here I am assigning some value to the variable gender at the time of initialization inside the initializer. If you are not using the init method to assign the variable gender some value, you should do any of the following:

  • you should either set the variable a default value at the time of declaration .
var gender:Gender = .male
  • Or, you should declare the variable as an optional.
var gender:Gender?

The objective is that, at the time of initialization of an instance , all the properties should be initialized.

Customizing Initialization

We can add custom initializers to a class with custom input parameters and optional property types.

We can have more than one initializers based on our requirement, with different parameter names, types etc..

struct Human {var gender:Gendervar age:Int = 10init(gender:Gender) { // initializer 1self.gender = gender}init(age:Int) { // initializer 2self.age = ageself.gender = .unknown}init(age:Int, gender:Gender) { // initializer 3self.age = ageself.gender = gender}}//------------------------------let human = Human(gender: .male)let human2 = Human(age: 20)let human3 = Human(age: 40, gender: .male)

The swift compiler will decide which init method to call based on the argument label.

Note that it is not possible to call these initializers without using argument labels. Argument labels must always be used in an initializer if they are defined, and omitting them is a compile-time error:

let human4 = Human() // error :cannot invoke initializer for type ‘Human’ with no argumentslet human5 = Human(40,.male) //error:error: missing argument labels 'age:gender:' in call

Initializer Parameters Without Argument Labels

If you do not want to use an argument label for an initializer parameter, write an underscore (_) instead of an explicit argument label for that parameter to override the default behavior.

Now, let’s add an init method to the Human class like this:

init(_ age:Int,_ gender:Gender) {self.age = ageself.gender = gender}

Now we can call:

let human5 = Human(40,.male)

Default Initializers

Swift provides a default initializer for any structure or class that provides default values for all of its properties and does not provide at least one initializer itself. The default initializer simply creates a new instance with all of its properties set to their default values.

This example defines a class called ShoppingListItem, which encapsulates the name, quantity, and purchase state of an item in a shopping list:

class ShoppingListItem {var name: String?var quantity = 1var purchased = false}var item = ShoppingListItem()

Memberwise Initializers for Structure Types

Structure types automatically receive a memberwise initializer if they do not define any of their own custom initializers.

Points to note:

  • If we don’t provide a custom initializer, the structure receives a memberwise initializer even if it has stored properties that do not have default values .
  • If we don’t provide a custom initializer, the structure receives a memberwise initializer even if it has stored properties that have default values.

Consider a struct called Size

struct Size {var width, height :Double // stored properties without default values}-------- or --------------
struct Size {
var width = 10.0, height = 30.0 // stored properties with default values}

The first structure has two stored properties with no default values. The second structure has two stored properties with default values given. Custom initializers are not given in both cases. To initialize this struct , we get a memberwise initializer with width and height as parameters:

let twoByTwo = Size(width: 2.0, height: 2.0)

If we provide a custom init method, all the stored properties should be initialized as explained earlier in this article. Also , the memberwise initializer will not be accessible in this case. ie; if you define a custom initializer for a value type, you will no longer have access to the default initializer (or the memberwise initializer, if it is a structure) for that type.

struct Size {var width, height :Doubleinit(){
self.width = 10.0
self.height = 30.0
}}let sizeObj1 = Size(width: 2.0, height: 2.0)// error. argument passed to call that takes no argumentslet sizeObj2 = Size() // success.

Initializer Delegation for Value Types

Initializers can call other initializers to perform part of an instance’s initialization. This process, known as initializer delegation.

  • Value types (structures and enumerations) do not support inheritance, and so their initializer delegation process is relatively simple, because they can only delegate to another initializer that they provide themselves.
  • Classes, can inherit from other classes, as described in Inheritance. This means that classes have additional responsibilities for ensuring that all stored properties they inherit are assigned a suitable value during initialization.

Example:

The following example defines a custom Rect structure to represent a geometric rectangle. The example requires two supporting structures called Size and Point, both of which provide default values of 0.0 for all of their properties:

struct Size {var width = 0.0, height = 0.0}struct Point {var x = 0.0, y = 0.0}

You can initialize the Rect structure below in one of three ways—by using its default zero-initialized originand size property values, by providing a specific origin point and size, or by providing a specific center point and size. These initialization options are represented by three custom initializers that are part of the Rectstructure’s definition:

struct Rect {var origin = Point()var size = Size()init() {}init(origin: Point, size: Size) {self.origin = originself.size = size}init(center: Point, size: Size) {let originX = center.x - (size.width / 2)let originY = center.y - (size.height / 2)self.init(origin: Point(x: originX, y: originY), size: size)}}

Designated Initializers and Convenience Initializers

All of a class’s stored properties — including any properties the class inherits from its superclass — must be assigned an initial value during initialization.

Swift defines two kinds of initializers for class types to help ensure all stored properties receive an initial value.

  • Designated initializers
  • Convenience initializers

Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.

Every class should have at least one designated initializer.

Designated initializers for classes are written in the same way as simple initializers for value types:

init( _parameters if any_ ) {
}

Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer’s parameters set to default values. You can also define a convenience initializer to create an instance of that class for a specific use case or input value type.

Convenience initializers are written in the same style, but with the convenience modifier placed before the init keyword, separated by a space:

convenience init( _parameters if any_ ) {
}

Let’s see an example : The class human has one designated init method and one convenience init method.

class HumanBeing {var name: Stringinit(name: String) {self.name = name}convenience init() {self.init(name: “not set”)// Convenience init call the designated init method}}let humanBeingObj1 = HumanBeing() // calls convenience initlet humanBeingObj2 = HumanBeing(name: “abhilash”) // calls designated init

Convenience init initializes the designated init method by calling self.init.

Another example for convenience init. 👆🏻

Initializer Delegation for Class Types

To simplify the relationships between designated and convenience initializers, Swift applies the following three rules for delegation calls between initializers:

  • A designated initializer must call a designated initializer from its immediate superclass. A designated initializer of a subclass cannot call a Convenience init from its superclass. If you try to do so, you will get an error saying “error: must call a designated initializer of the superclass”.
  • A convenience initializer must call another initializer from the same class.
  • A convenience initializer must ultimately call a designated initializer.

Have a look at the following image so that it will be more clear:

Image for post
Image for post
source: apple docs

Automatic Initializer Inheritance

Subclasses do not inherit their superclass initializers by default.However, superclass initializers are automatically inherited if certain conditions are met.

If we provide default values for any new properties you introduce in a subclass, the following two rules apply:

  • Rule 1 : If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
  • Rule 2: If your subclass provides an implementation of all of its superclass designated initializers — either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition — then it automatically inherits all of the superclass convenience initializers.

Consider the following example and the init methods available to create a Man class object.

class HumanBeing {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: “not set”)
// Convenience init call the designated init method
}
}

let humanBeingObj1 = HumanBeing() // calls convenience init
let humanBeingObj2 = HumanBeing(name: “abhilash”) // calls
designated init

____________________
class Man: HumanBeing {
var age:Int = 0
override init(name: String) {
super.init(name: name)
}
init(name: String, age:Int) {
super.init(name: name)
self.name = name
self.age = age
}
}
_______________________let manObj1 = Man() // calls convenience init of Human class
let manObj2 = Man(name: “Robert”) // calls overriden init
let manObj3 = Man(name: “John”, age: 10) // calls custom init
manObj1.name // prints “not set”
manObj2.name // prints “Robert”
manObj3.name // prints “John”

Where to go from here:

Initializers in swift part-2: Failable Initializers in swift

If you enjoyed reading this post and found it useful, please share and recommend it so others can find it 💚💚💚💚💚💚 !!!!

Thanks!!

Written by

iOS and tvOS developer, dreamer, photographer 🤨

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store