ProgrammingiOS/Swift Developer

What is Initializer Delegation in Swift, how do the delegation rules work between designated and convenience initializers, and how does it affect inheritance?

Pass interviews with Hintsage AI assistant

Answer.

Initializer Delegation is a system of initialization delegation built into Swift for classes. In Swift, there is historically a distinction between designated initializers (the primary initializer of a class responsible for fully initializing all properties) and convenience initializers (auxiliary, simplifying the creation of an instance with various parameter sets).

Problem: if chaotic execution of initializers between classes and their subclasses is allowed, there is a chance that the base class will not be fully initialized, or initialization will occur more than once, which will lead to an invalid object.

The solution is a strict rule:

  1. Designated initializer always calls the designated initializer of the superclass.
  2. Convenience initializer always calls another initializer of the same class (designated or another convenience).
  3. Convenience cannot directly invoke the superclass initializer.

Example code:

class Vehicle { var wheels: Int // Designated initializer init(wheels: Int) { self.wheels = wheels } // Convenience initializer convenience init() { self.init(wheels: 4) } } class Car: Vehicle { var color: String // Designated initializer init(wheels: Int, color: String) { self.color = color super.init(wheels: wheels) } // Convenience initializer convenience init(color: String) { self.init(wheels: 4, color: color) } }

Key features:

  • Minimal code duplication is required when creating instances with different parameter sets.
  • Support for safe class inheritance.
  • Guaranteed correct initialization of all properties of the object.

Tricky questions.

Question 1: Can a convenience initializer directly call a superclass initializer through super.init?

No, a convenience initializer always delegates initialization to another initializer of the current class, which can subsequently call the designated initializer of the superclass.

Question 2: What happens if a required initializer is not implemented in a subclass?

If the superclass has a required initializer, it must be overridden in each subclass (using required), otherwise the compiler will throw an error.

Question 3: What is the difference between convenience init and convenience required init?

convenience required init is required if a specific convenience initializer is declared as required in the superclass to ensure support for initialization in inheritance hierarchies.

Common mistakes and anti-patterns

  • Incorrect call chain of convenience/init and violation of property initialization.
  • Forgetting to implement the required initializer in a subclass.
  • Calling super.init from convenience, which is invalid.

Real-life example

Negative case

The developer called super.init from a convenience initializer. The code compiled only due to the absence of certain constraints, but at runtime, an error occurred: not all properties of the object were initialized.

Pros:

  • The code seemed clear to beginners as it resembled similar implementations in other languages.

Cons:

  • Bugs appeared with inheritance — child classes were not initialized correctly, causing the application to crash.
  • Refactoring was required.

Positive case

Well-structured designated and convenience initializers were used with explicit calls to each other. The logic was executed strictly according to Swift rules, so the initialization was always transparent and correct.

Pros:

  • Easy to extend and maintain class hierarchy.
  • Duplicate code was eliminated.
  • Testing was simplified.

Cons:

  • Careful documentation of initializer inheritance in large hierarchies was required.