Программирование SwiftSwift CoreРазработчик iOS на Swift

В классе convenience инициализатор пытается напрямую вызвать инициализатор суперкласса. Какое правило Swift...

В классе convenience-инициализатор пытается напрямую вызвать инициализатор суперкласса. Какое правило Swift нарушено?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

convenience-инициализатор обязан делегировать создание объекта другому инициализатору того же класса, а не напрямую вызывать инициализатор суперкласса. Поэтому вызов super.init из такого инициализатора запрещён.

Прямой вызов инициализатора суперкласса выполняется из designated-инициализатора подкласса. Convenience-инициализатор может только выбрать или подготовить удобный способ вызова designated-инициализатора своего класса.

Исторический контекст

Модель инициализации Swift разделяет инициализаторы на два уровня: designated отвечает за полное создание экземпляра, а convenience предоставляет альтернативный способ его сконструировать.

Такое разделение устраняет несколько независимых путей частичной инициализации и помогает гарантировать, что все хранимые свойства будут корректно установлены до завершения создания объекта.

Постановка проблемы

Если convenience-инициализатор напрямую обращается к super.init, он обходит обязательное делегирование внутри собственного класса. В результате логика инициализации, расположенная в designated-инициализаторе этого класса, может не выполниться.

Это опасно не только из-за нарушения синтаксического правила. Можно пропустить установку свойств, инварианты класса или подготовку состояния, на которую рассчитывают другие методы и инициализаторы.

Подробное решение

Designated-инициализатор подкласса должен сначала инициализировать все свойства, добавленные подклассом, затем вызвать designated-инициализатор суперкласса. После этого завершается вторая фаза инициализации, включая настройку унаследованного состояния.

Convenience-инициализатор не является самостоятельным маршрутом создания объекта. Он обязан вызвать другой инициализатор того же класса через self.init(...). Цепочка convenience-инициализаторов в итоге должна прийти к designated-инициализатору.

После делегирования convenience-инициализатор уже может выполнять дополнительную настройку доступных свойств. Но до делегирования он не может использовать self, обращаться к свойствам или вызывать методы экземпляра, поскольку объект ещё не прошёл необходимую инициализацию.

class Account { let name: String init(name: String) { self.name = name } convenience init() { self.init(name: "Гость") } } let account = Account()

Здесь init() не создаёт объект самостоятельно: он делегирует работу designated-инициализатору init(name:). Если бы init() пытался вызвать super.init(...), компилятор отклонил бы код, потому что convenience-инициализатор может делегировать только внутри текущего класса.

В подклассе designated-инициализатор может вызвать super.init, а convenience-инициализатор сначала делегирует designated-инициализатору этого же подкласса. Это сохраняет единый контролируемый путь инициализации.

Ситуация из практики

В базовом классе Account designated-инициализатор проверяет имя и устанавливает обязательное состояние. В подклассе PremiumAccount нужен короткий инициализатор без параметров.

Можно продублировать в convenience-инициализаторе код установки свойств и попытаться вызвать суперкласс. Минус такого подхода — нарушение правил Swift и риск расхождения логики при последующих изменениях.

Можно сделать короткий инициализатор designated и в нём вызвать super.init. Это корректно, но если инициализатор лишь подставляет значения по умолчанию, отдельный designated-путь может быть избыточным.

Оптимальный вариант — convenience-инициализатор PremiumAccount, который передаёт значения в designated-инициализатор PremiumAccount, а тот вызывает super.init. В итоге базовые проверки выполняются один раз, а все экземпляры проходят одинаковый корректный маршрут создания.

Что кандидаты часто упускают

  1. Может ли convenience-инициализатор присваивать свойства?

Да, но только после делегирования через self.init(...). До этого экземпляр ещё не считается инициализированным, поэтому доступ к его свойствам и методам запрещён. Кроме того, если свойство доступно только для чтения после инициализации, изменить его позднее нельзя.

  1. Обязан ли каждый convenience-инициализатор в конечном итоге вызвать designated-инициализатор?

Да. Цепочка делегирования может проходить через другие convenience-инициализаторы, но должна завершиться designated-инициализатором текущего класса. Иначе обязательная инициализация состояния не имеет гарантированной точки завершения.

  1. Почему convenience-инициализатор подкласса не должен напрямую вызывать super.init даже после установки собственных свойств?

Потому что его роль — предоставить альтернативный вход в инициализацию текущего класса, а не заменить designated-инициализатор. Прямой вызов super.init обходит designated-инициализаторы подкласса и может пропустить их инварианты. Если требуется такая последовательность, её следует реализовать в designated-инициализаторе подкласса, а convenience-инициализатор должен делегировать ему.