Swift: Initialization (Инициализация)

Подразделы:

Ключевое слово init
Инициализация — это процесс подготовки экземпляра класса, структуры или перечисления. Основная задача — проставление начальных значений для свойств. В отличии от Objective-C, методы инициализации ничего не возвращают.
Вспоминая главу Optionals (опциональные типы или опционалы) — экземплярам типа не может быть присвоено nil, экземпляр ОБЯЗАН иметь значение. Задача инициализатора проверить что все хранимые свойства к концу инициализации получат свои начальные значения (свойства могут быть nil только если они optional типа).
Кстати значения хранимым свойствам выставленные в процессе инициализации — не вызывают willSet/didSet даже если есть наблюдатели

1. свойству проставлено значение по умолчанию
2. у свойства нет значения по умолчанию, так что мы обязаны выставить его при инициализации (смотри сноску 5)
3. свойства имеющее optional тип, что инициализирует его пресловутым nil по умолчанию
4. Implicitly Unwrapped Optional — мы берем на себя ответственность, что на момент использования свойство будет иметь значение, но это снимает необходимость делать это в инициализаторе
6. если раскомментировать этот код — программа упадет с ошибкой, т.к. на этот момент goodsCount значения не имеет

Customizing Initialization — кастомизированная инициализация

Мы можем кастомизировать инициализацию с помощью переданных параметров. Т.к. init — по сути метод но ограниченный этим именем, все локальные имена параметров автоматически становятся внешними, тобишь обязательными. И все так же можно избежать необходимости использовать внешние имена с помощью символа подчеркивания _

В наследниках переназначать константы нельзя кстати

Default Initializers — инициализаторы по умолчанию

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

Такая возможность пропадет если создать init метод

Memberwise Initializers for Structure Types (Почленный инициализатор для структуры)

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

Это работает только если не создан нормальный init

Initializer Delegation for Value Types — Делегирование инициализаторов для значимых типов

У структур и перечислений все просто, т.к. не поддерживается наследование, поэтому единственный нюанс в возможности вызвать из параметризированного init другой init через self.init

Class Inheritance and Initialization (Наследование классов и инициализация)

С классами все несколько сложнее в плане инициализации, т.к. есть возможность наследования.
Существует 2 вида инициализаторов: designited (основной) и Convenience (вспомогательный). Суть в том, что designited — обычно один, от силы несколько, он базовый.

А convenience — вспомогательные, из них обычно вызываются основные

Существует 3 правила при инициализации
1) designited инициализатор в классе наследника обязан вызвать designited инициализатор его прямого суперкласса (нельзя вызвать инициализатор «деда», а не «отца»)
2) convenience инициализатор может вызывать другие инициализаторы только из этого же класса
3) convenience инициализатор обязан В ИТОГЕ вызывать designited инициализатор, т.е. convenience может вызвать convenience, и сколько угодно раз так может продолжаться, но в итоге должен произойти вызов именно designited инициализатора

Two-Phase Initialization — двухфазовая инициализация

Инициализация классов делится на 2 фазы:
1) Класс должен выставить начальные значения всем хранимым свойствам которые именно он создает.
2) Каждый класс получает возможность переназначить значения хранимых свойств в направлении от базового класса к наследникам

Компилятор swift проводит 4 проверки безопасности для того, чтобы убедиться что правила не нарушены
Проверка 1.
Designited инициализатор обязан выставить значение для всех свойств введенных в этом классе до вызова super.init

Проверка 2
В Designited инициализаторах нельзя присваивать значения свойствам базового класса до вызова super.init

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

Проверка 4
Convenience инициализатор не имеет права вызывать метод до вызова другого инициализатора.

Вот пример где показаны типовые ошибки

1) если бы этого присваивания не было — вылетела ошибка в процессе проверки 1
2) это выявляется во время 2й проверки, попытка присвоить свойство базового классы до вызова super.init
3) выявляет 3я проверка, обращение к свойству до вызова другого инициализатора
4) выявляет 4я проверка, вызов метода до вызова другого инициализатора

По сути процесс инициализации сначала выполняем наши 4 проверки у инициализируемого класса, затем у его родителя и так пока не дойдем до класса у которого нет родителя.
После этого начинается кастомизация и мы возвращаемся к инициализируемому классу — по ходу прохождения выполняем кастомизацию

В общем случае инициализаторы не наследуются автоматически, если у потомка планируется инициализатор с той же сигнатурой — мы обязаны его переопределить, если переопределяемым инициализатором будет выступать designited — необходимо использовать ключевое слово override, если convenience — этого делать не нужно

Но, есть частные случаи, когда потомок все-таки наследует все или часть инициализаторов
1) Если потомок не создает новых designited инициализаторов (что означает, что он не вводит новых свойств без значения по умолчанию ) — он наследует все designited инициализаторы своего родителя
2) Если потомок предоставляет реализацию для ВСЕХ designited инициализаторов родителя, либо не вводит их вообще (как в пункте 1) — он наследует все convenience инициализаторы своего родителя

1) Этот вызов возможен, т.к. мы не определили новых designited инициализаторов, следовательно выполнили пункт 1 и получили доступ ко всем designited инициализаторам родителя, что привело к автоматическому выполнению условий необходимых для пункта 2, что позволило нам получить доступ к convenience инициализаторам родителя

2) Этот вызов возможен, т.к. мы переопределил ВСЕ designited инициализаторы родителя (пусть и сделали их convenience), т.е. выполнили условия 2го пункта, что позволило нам получить доступ к convenience инициализаторам родителя

Failable Initializers (Проваливающиеся инициализаторы)

Инициализаторы с возможностью выдать ошибку и не завершить процесс инициализации
Решается это с помощью пометки инициализатора вопросительным знаком (init?), чтобы провалить инициализацию — необходимо внутри init? сделать return nil

Если перечисление использует сырое значение — оно автоматически получает инициализатор init?(rawValue:)

В документации и для Swift 1.2 и для Swift 2.0 указано, что если значимый тип может вернуть nil в любом месте инициализатора, то в классе такое позволено только после того как все свойства этого класса получат свои значения

1 — компилятор Swift 1.2 выдаст ошибку, т.к. на этот момент свойства name и length еще не получили своих значений.


Но в Swift 2.0 убрали требование на выставление всех свойств до выброса nil но только для convenience инициализаторов


2 — как видим из init? мы можем вызывать обычные инициализаторы, как из этого же класса, так и из родительского

init? можно переопределять в потомках, при переопределении можно сделать failable инициализатор — обычным. Из обычного init нельзя вызывать failable

Так же можно определить init!, правила использование те же, но после получения обьекта не нужно его будет приводить к конкретному типу

Required Initializers — требуемые инициализаторы

Если в родительском классе инициализатор обозначен как требуемый с помощью ключевого слова required — во всех детях он должен быть так же быть проимплементирован: по умолчанию (если не назначено новых designited инициализаторов), и напрямую (с указанием required), если designited инициализатор был добавлен

Этот код будет выдавать ошибку, т.к. мы добавили designited инициализатор, и не переопределили required init. Убрать ошибку можно 2мя способами.
Или закомментировать init(str: String), таким образом required init будет использоваться родительский, или раскоментировать required init, в таком случае мы явно выполним требование в переопределении инициализатора

Setting a Default Property Value with a Closure or Function (Выставление значения по умолчанию для хранимого свойства с помощью замыкания или функции)

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

Вот реальный пример, где массив arr инициализируется при создании класса 15ю степенями двойки.

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *