ПрограммированиеSenior iOS разработчик

Как реализуется инициализация свойств в Swift? Чем отличаются designated, convenience и required инициализаторы? Какие нюансы возникают при работе с иерархиями инициализации?

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

Ответ.

В Swift все свойства нес optional-типов должны быть инициализированы до конца инициализации экземпляра. Для этого используются инициализаторы: designated (основные), convenience (вспомогательные) и required (обязательные для наследников).

  • Designated инициализатор — основной, отвечает за инициализацию всех свойств класса и вызов designated инициализатора суперкласса. У каждого класса минимум один designated инициализатор.
  • Convenience инициализаторы — вспомогательные, делегируют инициализацию этому же классу (вызывают другие инициализаторы внутри класса через self.init(...)), упрощая создание объекта с различными наборами параметров.
  • Required инициализатор — требует, чтобы все подклассы реализовали этот инициализатор. Объявляется с модификатором required.

Пример:

class Vehicle { let numberOfWheels: Int required init(numberOfWheels: Int) { self.numberOfWheels = numberOfWheels } } class Car: Vehicle { let brand: String // Designated initializer required init(numberOfWheels: Int) { self.brand = "Unknown" super.init(numberOfWheels: numberOfWheels) } // Convenience convenience init() { self.init(numberOfWheels: 4) self.brand = "Ford" } }

Ключевые особенности:

  • Все свойства должны быть инициализированы до обращения к super.init.
  • Convenience инициализаторы всегда должны вызывать designated или другой convenience инициализатор self.
  • Required инициализаторы обязаны быть реализованы во всех наследниках.

Вопрос с подвохом.

В каком порядке необходимо инициализировать свойства подкласса и суперкласса при использовании designated initializer?

Ответ: Все свойства подкласса должны быть проинициализированы до вызова designated инициализатора суперкласса (super.init), иначе будет ошибка компиляции. После super.init нельзя инициализировать новые stored-свойства подкласса, только использовать их.

class Parent { let parentProp: Int init(prop: Int) { parentProp = prop } } class Child: Parent { let childProp: String init(prop: Int, childProp: String) { self.childProp = childProp // Сначала свои свойства super.init(prop: prop) // Потом parent } }

Примеры реальных ошибок из-за незнания тонкостей темы.


История

Разработчик в convenience инициализаторе попытался напрямую присвоить значение require-свойству, не вызвав designated. В результате свойство было инициализировано дважды с разными значениями, что вело к неожиданному поведению при тестировании.


История

При наследовании deep class hierarchy забыли объявить required init, что привело к невозможности корректной инициализации производного типа из фреймворка через reflection, и крашу при десериализации JSON-модели.


История

В приложении во время расширения класса Vehicle не был вызван designated инициализатор суперкласса, что привело к некорректной инициализации обязательных свойств и runtime crash в production после обновления схемы данных.