Required and convenience initializers

You already know it’s possible to have multiple initializers in a class, which means you could potentially call any of those initializers from a subclass.

Often, you’ll find that your classes have various initializers that simply provide a “convenient” way to initialize an object:

class Student {

let firstName: String let lastName: String var grades: [Grade] = []

init(firstName: String, lastName: String) { self.firstName = firstName

self.lastName = lastName

}

init(transfer: Student) { self.firstName = transfer.firstName self.lastName = transfer.lastName

}

func recordGrade(_ grade: Grade) { grades.append(grade)

}

}

In this example, the Student class can be built with another Student object. Perhaps the student switched majors? Both initializers fully set the first and last names.

Subclasses of Student could potentially rely on the Student-based initializer when they make their call to super.init. Additionally, the subclasses might not even provide a method to initialize with first and last names. You might decide that the first and last name-based initializer is important enough that you want it to be available to all subclasses.

Swift supports this through the language feature known as required initializers:

class Student {

let firstName: String let lastName: String var grades: [Grade] = []

required init(firstName: String, lastName: String) { self.firstName = firstName

self.lastName = lastName

}

//…

}

In the modified version of Student above, the first and last name-based initializer has been marked with the keyword required. This keyword will force all subclasses of Student to implement this initializer.

Now that there’s a required initializer on Student, StudentAthlete must override and implement it too.

class StudentAthlete: Student {

// Now required by the compiler!

required init(firstName: String, lastName: String) { self.sports = []

super.init(firstName: firstName, lastName: lastName)

}

//…

}

Notice how the override keyword isn’t required with required initializers. In its place, the required keyword must be used to make sure that any subclass of StudentAthlete still implements this required initializer.

You can also mark an initializer as a convenience initializer:

class Student {

convenience init(transfer: Student) {

self.init(firstName: transfer.firstName, lastName: transfer.lastName)

}

//…

}

The compiler forces a convenience initializer to call a non-convenience initializer (directly or indirectly), instead of handling the initialization of stored properties itself. A non-convenience initializer is called a designated initializer and is subject to the rules of two-phase initialization. All initializers you’ve written in previous examples were in fact designated initializers.

You might want to mark an initializer as convenience if you only use that initializer as an easy way to initialize an object, but you still want it to leverage one of your designated initializers.

Here’s a summary of the compiler rules for using designated and convenience initializers:

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

results matching ""

    No results matching ""