ProgrammingSenior iOS Developer

How is property initialization implemented in Swift? What are the differences between designated, convenience, and required initializers? What nuances arise when working with initialization hierarchies?

Pass interviews with Hintsage AI assistant

Answer.

In Swift, all properties of non-optional types must be initialized by the end of the instance initialization. For this, initializers are used: designated (primary), convenience (auxiliary), and required (mandatory for subclasses).

  • Designated initializer — the main one, responsible for initializing all properties of the class and calling the designated initializer of the superclass. Each class has at least one designated initializer.
  • Convenience initializers — auxiliary ones that delegate the initialization to the same class (calling other initializers within the class using self.init(...)), simplifying the creation of an object with various sets of parameters.
  • Required initializer — requires all subclasses to implement this initializer. Declared with the required modifier.

Example:

class Vehicle { let numberOfWheels: Int required init(numberOfWheels: Int) { self.numberOfWheels = numberOfWheels } } class Car: Vehicle { let brand: String // Designated initializer required init(numberOfWheels: Int) { self.brand = "Unknown" super.init(numberOfWheels: numberOfWheels) } // Convenience convenience init() { self.init(numberOfWheels: 4) self.brand = "Ford" } }

Key features:

  • All properties must be initialized before accessing super.init.
  • Convenience initializers must always call a designated or another convenience initializer using self.
  • Required initializers must be implemented in all subclasses.

Trick question.

In what order should subclass and superclass properties be initialized when using a designated initializer?

Answer: All properties of the subclass must be initialized before calling the designated initializer of the superclass (super.init), otherwise, it will cause a compilation error. After super.init, no new stored properties of the subclass can be initialized, only used.

class Parent { let parentProp: Int init(prop: Int) { parentProp = prop } } class Child: Parent { let childProp: String init(prop: Int, childProp: String) { self.childProp = childProp // First your properties super.init(prop: prop) // Then parent } }

Examples of real errors due to ignorance of the nuances of the topic.


Story

A developer in a convenience initializer tried to directly assign a value to a required property without calling designated. As a result, the property was initialized twice with different values, leading to unexpected behavior during testing.


Story

When inheriting a deep class hierarchy, a required init was forgotten to be declared, which led to the inability to correctly initialize the derived type from the framework via reflection, and a crash during JSON model deserialization.


Story

In an application, during the extension of the Vehicle class, the designated initializer of the superclass was not called, leading to incorrect initialization of mandatory properties and a runtime crash in production after the data schema update.