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

Ответ

convenience — обозначает вспомогательный (вторичный) инициализатор, который должен внутри вызвать designated (назначенный) инициализатор того же класса. Упрощает создание объекта с альтернативными параметрами или значениями по умолчанию.

required — обязывает все прямые подклассы реализовать данный инициализатор. Гарантирует, что определённая логика инициализации будет доступна во всей иерархии наследования.

Основные отличия:

convenience required
Назначение Упрощение инициализации Контракт для подклассов
Вызов Должен вызвать self.init Может быть как designated, так и convenience
Наследование Может быть переопределён в подклассе Обязателен для реализации в подклассе
Использование Альтернативные параметры, значения по умолчанию Протоколы, классовые типы, фабричные методы

Пример convenience инициализатора:

class Document {
    var title: String
    var body: String

    // Designated инициализатор
    init(title: String, body: String) {
        self.title = title
        self.body = body
    }

    // Convenience инициализатор
    convenience init(title: String) {
        self.init(title: title, body: "") // Вызов designated
    }

    // Ещё один convenience
    convenience init() {
        self.init(title: "Untitled") // Вызов другого convenience
    }
}

let doc1 = Document(title: "Report", body: "Content") // Designated
let doc2 = Document(title: "Note") // Convenience
let doc3 = Document() // Convenience -> Convenience -> Designated

Пример required инициализатора:

class Vehicle {
    let wheels: Int

    // Все подклассы должны реализовать этот init
    required init(wheels: Int) {
        self.wheels = wheels
    }
}

class Car: Vehicle {
    let brand: String

    init(brand: String) {
        self.brand = brand
        super.init(wheels: 4)
    }

    // Обязательная реализация required инициализатора
    required init(wheels: Int) {
        self.brand = "Unknown"
        super.init(wheels: wheels)
    }
}

class Bicycle: Vehicle {
    // Тоже должен реализовать required init
    required init(wheels: Int) {
        super.init(wheels: wheels)
    }
}

// Использование с классовой переменной (метаклассом)
func createVehicle(ofType type: Vehicle.Type) -> Vehicle {
    return type.init(wheels: 2) // Требуется required init
}

let bike = createVehicle(ofType: Bicycle.self)

Важно:

  • convenience инициализатор не может вызвать другой convenience инициализатор суперкласса, только designated.
  • required модификатор часто сочетается с convenience, если нужен фабричный метод в иерархии.
  • При переопределении required инициализатора в подклассе также нужно указывать required (а не override).

Ответ 18+ 🔞

А, ну вот, опять эти ваши convenience с required! Слушай, давай разберёмся, как есть, без этих ваших академических закидонов.

Представь, что у тебя есть класс, ну, скажем, Пицца. У неё есть главный, матерый инициализатор — designated. Это как заказ пиццы «Маргарита» с полным списком: тесто, сыр, соус, оливкове масло, базилик — всё по полной программе, блядь. Без этого — пиццы нет.

class Пицца {
    var тесто: String
    var сыр: String
    var соус: String

    // Вот он, designated, царек, блядь. Без него нихуя.
    init(тесто: String, сыр: String, соус: String) {
        self.тесто = тесто
        self.сыр = сыр
        self.соус = соус
    }
}

А теперь, сука, convenience — это когда ты ленивая жопа и не хочешь каждый раз указывать весь этот овердохуище параметров. Хочешь просто сказать: «Дайте пиццу "Четыре сыра"» — а там уже внутри само подставится, какой сыр и соус. Удобняшка, блядь!

class Пицца {
    var тесто: String
    var сыр: String
    var соус: String

    init(тесто: String, сыр: String, соус: String) {
        self.тесто = тесто
        self.сыр = сыр
        self.соус = соус
    }

    // А вот convenience — ленивая жопа в действии!
    convenience init(название: String) {
        switch название {
        case "Четыре сыра":
            self.init(тесто: "Тонкое", сыр: "Микс", соус: "Сливочный")
        case "Пепперони":
            self.init(тесто: "Толстое", сыр: "Моцарелла", соус: "Томатный")
        default:
            self.init(тесто: "Обычное", сыр: "Моцарелла", соус: "Томатный")
        }
    }

    // Или вот ещё ленивее — пицца по умолчанию, а то нихуя не выбрал
    convenience init() {
        self.init(название: "Маргарита") // Смотри, блядь, вызывает другой convenience!
    }
}

Важный момент, ёпта: convenience инициализатор обязан где-то внутри вызвать designated инициализатор того же класса — self.init(...). Не может он просто так, с бухты-барахты, свойства назначить. Иначе компилятор тебе такую ошибку влепит — мало не покажется.


А теперь про required. Это, блядь, уже не про лень, а про жёсткий контракт, ёбаный в рот! Ты как родитель-класс говоришь: «Слушай сюда, все мои будущие дети-подклассы! Какой бы ты, пидарас шерстяной, ни был, у тебя ДОЛЖЕН быть вот этот инициализатор! И точка, нахуй!»

Чаще всего это нужно, когда ты работаешь с классом как с типом (через SomeClass.self) или в протоколах.

class Транспорт {
    let колёса: Int

    // required, ёпта! Все наследники обязаны это реализовать.
    required init(колёса: Int) {
        self.колёса = колёса
    }
}

class Велосипед: Транспорт {
    // Блядь, раз родитель сказал required — делаем, и не рыпайся.
    // Заметь, тут даже 'override' не нужен, только required.
    required init(колёса: Int) {
        super.init(колёса: колёса)
    }
}

class Машина: Транспорт {
    var марка: String

    init(марка: String) {
        self.марка = марка
        super.init(колёса: 4) // у машины обычно 4 колеса, логично же
    }

    // А вот и required реализация! Даже если есть свой инициализатор.
    required init(колёса: Int) {
        self.марка = "Неизвестно"
        super.init(колёса: колёса)
    }
}

И вот где это, блядь, выстреливает: когда у тесть фабрика или общий код, который создаёт объекты через метакласс.

func сгенерируйТранспорт(типа: Транспорт.Type, колёса: Int) -> Транспорт {
    // Без required init тут была бы пизда — компилятор не уверен, что у подкласса он есть.
    return типа.init(колёса: колёса) // А так — всё ок, required гарантирует!
}

let велик = сгенерируйТранспорт(типа: Велосипед.self, колёса: 2)

Итожу, блядь:

  • convenience — это твой ленивый кореш, который всегда ищет короткий путь, но в итоге всё равно зовёт главного (designated).
  • required — это ваш строгий отец, который заставляет всех детей иметь одинаковую семейную черту (инициализатор), чтобы потом не было сюрпризов, нахуй.

И запомни: convenience может вызывать другой convenience, но цепочка ВСЕГДА должна упереться в designated. А required может быть как designated, так и convenience — главное, чтобы в подклассах его реализовали, иначе пиши пропало.