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).
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:
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 } }
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.