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

Ответ

Требование required гарантирует, что все будущие подклассы данного класса также будут иметь этот инициализатор, обеспечивая полное и корректное соответствие протоколу на всём дереве наследования.

Причина: Протокол определяет контракт. Если класс A соответствует протоколу P, то любой экземпляр A (включая экземпляры его подклассов B, C) должен быть создаваемым через инициализатор из P. Без required подкласс мог бы не реализовать этот инициализатор, нарушив контракт протокола.

protocol Configurable {
    init(config: Configuration) // Требует инициализатор
}

class BaseViewController: UIViewController, Configurable {
    // required обязательно, чтобы подклассы тоже его реализовали
    required init(config: Configuration) {
        super.init(nibName: nil, bundle: nil)
        // ... настройка
    }

    required init?(coder: NSCoder) { /* ... */ }
}

class ProfileViewController: BaseViewController {
    // ProfileViewController НЕЯВНО наследует required init(config:)
    // и соответствует протоколу Configurable.
    // Если бы в BaseViewController не было `required`,
    // ProfileViewController мог бы не иметь этого инициализатора.
}

Исключение: Класс, помеченный как final, не может иметь подклассов, поэтому для него модификатор required можно опустить — требование протокола выполняется только на уровне этого класса.

final class Utility: Configurable {
    // `required` не нужно, так как наследования не будет
    init(config: Configuration) { /* ... */ }
}

Для структур (struct) и перечислений (enum) модификатор required не применяется, так как они не поддерживают наследование.

Ответ 18+ 🔞

Смотри, тут такая тема, про которую можно мозг сломать, если не въехать. Ну, я попробую объяснить, как есть, без этих ваших заумных загибов.

Представь, что протокол — это такой контракт, бумажка с печатью. Там чётко написано: «Все, кто под этим контрактом ходит, должны уметь создаваться вот таким конкретным способом». Способ этот — инициализатор init(config:).

Допустим, у тебя есть главный класс BaseViewController. Он подписал этот контракт, то есть сказал: «Да, я умею так создаваться, вот мой init(config:)».

А теперь вопрос на миллион: что будет с его сыном, классом ProfileViewController, который от него наследуется? По логике, сын же всё от отца перенимает, включая и контракты. Значит, он тоже должен уметь создаваться через этот самый init(config:). Иначе получается пиздец: объект типа ProfileViewController формально соответствует протоколу (через папу), но реально создать его по правилам протокола не выйдет — инициализатора-то нет! Контракт хуями прикрыт.

Вот чтобы этого не случилось, в свифте и придумали модификатор required. Когда ты ставишь его перед инициализатором в классе, ты буквально говоришь системе: «Слушай сюда, все мои будущие дети, внуки и правнуки — вы ОБЯЗАНЫ иметь этот инициализатор. Без вариантов. Иначе компилятор вас нахуй пошлёт».

protocol Configurable {
    init(config: Configuration) // Контракт: умей так создаваться.
}

class BaseViewController: UIViewController, Configurable {
    // required ОБЯЗАТЕЛЕН! Чтобы дети не выёбывались.
    required init(config: Configuration) {
        super.init(nibName: nil, bundle: nil)
        // ... настройка
    }

    required init?(coder: NSCoder) { /* ... */ } // Это от UIViewController
}

class ProfileViewController: BaseViewController {
    // Всё ок. Этот класс НЕЯВНО унаследовал `required init(config:)` от папаши.
    // Он соответствует протоколу, даже если тут пусто.
}

А теперь представь, что в BaseViewController не было бы required. Тогда ProfileViewController мог бы спокойно сказать: «А я этот инициализатор не хочу наследовать, у меня свой путь». И создал бы себе другой. И получился бы пиздец: объект есть, протокол вроде как соблюдён, а создать его по правилам протокола — нихуя. Компилятор такого не пропустит, слава богу.

Ну а теперь про исключения, их два:

  1. final класс. Это такой упёртый мудак, который заявил: «Детей у меня не будет и быть не может!». Ну раз детей нет, то и требовать от них нечего. Поэтому для final класса можно required не писать. Контракт-то он сам выполняет.

    final class Utility: Configurable {
        // `required` тут не нужно. Класс конечный, наследования не будет.
        // Просто выполняем контракт для себя.
        init(config: Configuration) { /* ... */ }
    }
  2. Структуры (struct) и перечисления (enum). Это вообще отдельная каста. Они как бесполые амебы — размножаются не через наследование, а через деление. У них нет понятия «дети» в классическом ООП-шном смысле. Поэтому модификатор required к ним вообще не применяется, он для них как манда невидимая. Они просто реализуют инициализатор из протокола, и всё.

Короче, суть вот в чём: required — это страховка от распиздяйства в будущем. Это способ заставить всё будущее потомство класса играть по тем же правилам, что и он сам. Без этого можно было бы легко наломать дров в большой иерархии классов. А так — всё чётко, предсказуемо и компилируется.