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

От чего зависит автоматическое наследование инициализаторов суперкласса подклассом в Swift?

От чего зависит автоматическое наследование инициализаторов суперкласса подклассом в Swift?

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

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

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

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

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

Правила решают две связанные задачи: не заставлять разработчика вручную дублировать безопасные инициализаторы и одновременно не допускать создание подкласса с неинициализированными собственными свойствами.

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

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

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

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

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

Рассмотрим основные правила.

  1. Если все новые хранимые свойства подкласса имеют значения по умолчанию, а подкласс не объявляет designated-инициализаторов, он наследует все designated-инициализаторы суперкласса.
  2. Если подкласс реализует все designated-инициализаторы суперкласса, он наследует все convenience-инициализаторы суперкласса.
  3. Собственный convenience-инициализатор не препятствует наследованию designated-инициализаторов. Препятствием является именно собственный designated-инициализатор.
  4. Добавление свойства без значения по умолчанию требует собственного designated-инициализатора, который задаст это свойство до вызова инициализатора суперкласса.
class Screen { let title: String init(title: String) { self.title = title } convenience init() { self.init(title: "Default") } } class DetailScreen: Screen { let identifier = 1 } let a = DetailScreen(title: "Details") let b = DetailScreen()

В примере DetailScreen не объявляет designated-инициализатор, а identifier имеет значение по умолчанию. Поэтому он наследует init(title:), а затем по второму правилу — и convenience-инициализатор init().

Если бы DetailScreen объявил собственный designated-инициализатор, init(title:) не унаследовался бы автоматически. Для наследования convenience-инициализатора пришлось бы явно реализовать или корректно унаследовать все designated-инициализаторы суперкласса.

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

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

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

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

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

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

1. Блокирует ли convenience-инициализатор подкласса наследование designated-инициализаторов?

Нет. Convenience-инициализатор не нарушает первое правило. Если все новые свойства имеют значения по умолчанию и собственных designated-инициализаторов нет, designated-инициализаторы суперкласса всё ещё могут быть унаследованы.

Важно различать вид инициализатора, а не просто факт наличия init в теле класса. Наличие собственного convenience-инициализатора не равно объявлению собственного designated-инициализатора.

2. Что произойдёт, если у всех свойств есть значения по умолчанию, но подкласс объявил один designated-инициализатор?

Автоматическое наследование designated-инициализаторов суперкласса не произойдёт. Наличие значений по умолчанию уже недостаточно, потому что подкласс явно задал собственную designated-инициализацию.

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

3. Как required связан с автоматическим наследованием инициализаторов?

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

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