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

Ответ

Инициализаторы в 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

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

Ответ 18+ 🔞

А, ну вот, опять эти инициализаторы в Swift, блядь! Сидишь такой, думаешь — ну что за ёперный театр, нахуй столько правил? А потом бац — и оказывается, там всё логично, просто мозги надо включить, а не в рот себе чих-пых!

Смотри, есть два типа этих ваших инициализаторов, и они как отец и сын — один главный, а другой ленивый по жизни.

Designated (Назначенные) — это как главный пахан. Его работа — всё проинициализировать до конца, чтоб ни одно свойство не осталось без значения, как сирота казанская. А если это класс-наследник, то он ещё и суперклассу должен позвонить, типа «батя, я тут всё сделал, твою часть тоже проставь». И может он звонить только своему прямому предку, а не через голову какому-нибудь прадеду. И такой пахан в классе должен быть хотя бы один, а то кто порядок наведёт?

Convenience (Вспомогательные) — это как хитрая жопа. Его задача — сделать тебе красиво и удобно. Не хочешь все параметры вписывать? На, вот тебе инициализатор с дефолтными значениями. Он сам-то нихуя не делает по сути, только в итоге обязан позвать designated своего же класса, а не чужого. То есть он как менеджер-посредник: берёт твои упрощённые данные, подготавливает, а потом — «Герасим, на, разбирайся!».

Правила, блядь, простые, но железные:

  1. Designated зовёт designated своего родителя (если он есть).
  2. Convenience зовёт другой инициализатор своего же класса.
  3. И вся эта цепочка 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. Бате отзвонился
    }
    // CONVENIENCE, который переопределяет родительский
    override convenience init(name: String) {
        self.init(name: name, quantity: 1) // И тоже зовёт designated СВОЕГО класса!
    }
}

// А теперь используем эту красоту:
let a = RecipeIngredient() // По цепочке: Food.convenience() -> RecipeIngredient.init(name:quantity:)
let b = RecipeIngredient(name: "Salt") // RecipeIngredient.convenience(name:) -> тот же designated
let c = RecipeIngredient(name: "Eggs", quantity: 6) // Напрямую в designated

И вся эта, блядь, карусель нужна для одного — чтобы твой объект родился здоровым и полноценным, со всеми проинициализированными свойствами. Чтобы не вышло так, что ты его уже используешь, а у него внутри какое-то свойство nil болтается, как манда с ушами. Swift этого не любит, он за безопасность, сука. Так что учи правила, не распиздуйся!