В чем разница между designated и convenience инициализаторами в Swift?

«В чем разница между designated и convenience инициализаторами в Swift?» — вопрос из категории Swift Core, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Инициализаторы в Swift делятся на два четких типа, образующих иерархию делегирования для гарантии полной инициализации всех свойств.

Designated (Назначенные) Инициализаторы

  • Основная роль: Полностью инициализируют все свойства, введенные классом, и вызывают инициализатор суперкласса (для классов), чтобы продолжить цепочку инициализации вверх.
  • Делегирование: Могут вызывать только designated инициализаторы своего непосредственного суперкласса.
  • Количество: Класс должен иметь как минимум один designated инициализатор.

Convenience (Вспомогательные) Инициализаторы

  • Основная роль: Предоставляют альтернативные, более удобные способы создания экземпляра. Они не обязаны полностью инициализировать свойства самостоятельно.
  • Делегирование: Должны в конечном счете вызвать designated инициализатор того же класса (не суперкласса!).
  • Цель: Установка значений по умолчанию, преобразование типов, предоставление более ясного API.

Правила делегирования

  1. Designated → Superclass: Designated инициализатор должен вызвать designated инициализатор суперкласса (в классах).
  2. Convenience → Self: Convenience инициализатор должен вызвать другой инициализатор того же класса.
  3. Convenience → Designated: Цепочка вызовов convenience инициализаторов должна в итоге привести к вызову designated инициализатора того же класса.

Пример иерархии

class Food {
    var name: String
    // DESIGNATED инициализатор
    init(name: String) {
        self.name = name
    }
    // CONVENIENCE инициализатор
    convenience init() {
        self.init(name: "[Unnamed]") // Вызывает designated своего класса
    }
}

class RecipeIngredient: Food {
    var quantity: Int
    // DESIGNATED инициализатор подкласса
    init(name: String, quantity: Int) {
        self.quantity = quantity // 1. Инициализируем свои свойства
        super.init(name: name)   // 2. Вызываем designated суперкласса
    }
    // CONVENIENCE инициализатор (переопределяет convenience суперкласса)
    override convenience init(name: String) {
        self.init(name: name, quantity: 1) // Вызывает designated СВОЕГО класса
    }
}

// Использование:
let a = RecipeIngredient() // Использует convenience Food.init() -> RecipeIngredient.init(name:quantity:)
let b = RecipeIngredient(name: "Salt") // Использует convenience RecipeIngredient.init(name:)
let c = RecipeIngredient(name: "Eggs", quantity: 6) // Использует designated

Почему это важно? Эти правила обеспечивают безопасность и предсказуемость процесса инициализации, гарантируя, что все свойства будут проинициализированы до первого использования объекта, даже в сложных иерархиях наследования.