init swift что это такое
Инициализаторы Swift — просто о сложном
Для программиста очень важно понимать что такое инициализация. И не только для iOS программирования. В объектно-ориентированных языках, как следует из их названия, работа происходит с объектами, и для этой работы объекты нужно создать, или инициализировать. Я прекрасно понимаю, что ни для кого «не открыл Америку», написав эти несколько строк. Однако, по своему опыту скажу, что до конца разобраться в процедуре инициализации в Swift достаточно сложно.
В этой статье я попытаюсь простыми словами поговорить об особенностях работы методов init(), немного затрону проваливающиеся (fail) инициализаторы init? (некоторые программисты еще их называют опциональными) и расскажу об инициализации из файла через aCoder и aDecoder
Начну с того, что для языка Swift установлены вполне определенные правила инициализации, правила отношений между инициализаторами объектов, которые были унаследованы, правила вызова инициализаторов, если их несколько у одного объекта. Эти правила описаны в документации Apple, я даже встречал их на русском языке. Но приводить их тут не буду по нескольким причинам:
Но! Советую правила прочитать, держать их под рукой, т.к. они однозначно определяют требования к инициализации.
Итак вот стандартный метод написания инициализатора в Swift:
The main purpose of an init method is to fill in the object’s instance variables. Any
instance variables or constants that do not have a value yet must be given one in
the init method.
Swift does not allow variables or constants to have no value (except for optionals),
and init is your last chance to make this happen.
Давайте рассмотрим пример:
Также есть инициализатор init(wheelDiametr:) который запрашивает размер резины и это запрошенное значение устанавливает в свойство wheelDiametr.
Так вот у нашего класса два! инициализатора. Один который мы написали, а второй который Swift за нас любезно добавит. Это init(). И не смотря на то что мы его не написали, он все равно есть. Мы его описали тогда,, когда устанавливали значения в свойства. Т. е. если написать…
… строка fw14 = FourWheel() вызовет инициализатор init(). Объект будет создан, и его свойства будут wheelDiametr = 14 и season = «Winter».
А вот если написать …
… то строка кода вызывает написанный нами инициализатор, который запрашивает диаметр колеса. И так как мы передаем в него число 18 то свойства объекта fwh18 будут wheelDiametr = 18 и season = «Winter».
Внимательный читатель спросит, а где же тот «стандартный» пример, который был озвучен в начале статьи? Мы разобрали уже два инициализатора и ни один не был похож на первый пример. Так и есть! Просто вся соль заключается в инициализации объектов классов, которые являются наследниками других классов.
Класс «Машина» является наследником класса «Четырехколесный», а значит объект этого класса имеет все свойства своего суперкласса плюс еще своих два — name и type. И для инициализации объекта класса Car мы будем использовать инициализатор init(name: type:).
При вызове этого инициализатора произойдет следующее: в свойства name и type установятся значения, переданные в инициализатор ( в данном случае это «Lada» и «Sedan»), а затем сработает инициализатор суперкласса — в котором в свою очередь установятся свойства wheelDiametr и season. Установятся они в значения 14 и «Winter», т.к. инициализатор init() задает именно эти значения. А потом произойдет вызов метода changeWheel() и свойство season будет иметь значение «Summer».
В данном примере мы провели инициализацию по схеме, обозначенной в начале статьи. При разработке реальных iOS-приложений на придется иметь дело с классами из фреймворков, написанных Apple, т.е. наши классы будут подклассами исходных классов. Соответственно инициализация будет происходить по вышеуказанному принципу.
Классы в Swift [Часть 1]
Недавно Apple представила общественности достаточно важное изменение в разработке iOS приложений, анонсировав новый язык программирования Swift. В настоящее время, количество материалов на русском, посвящённых этому языку, ограничено. Также Swift — язык объектно-ориентированный, и классы в нём — основа основ. Поэтому я решил перевести эту статью.
Создание классов
Инициализаторы
Инициализация — процесс подготовки экземпляра класса, структуры или перечисления для дальнейшего использования. Он включает в себя инициализацию каждого свойства и выполнение других настроек или инициализаций, необходимых до первого использования.
Процесс инициализации реализуется с помощью инициализаторов, которые являются специальными методами, вызывающимися при объявлении новых экземпляров класса. В отличие от Objective-C, в Swift инициализаторы ничего не возвращают, т.к. они обеспечивают правильность инициализации новых экземпляров класса.
Обратимся к примеру
Запустив его, вы получите три ошибки компиляции
Чтобы исправить ситуацию, давайте зададим тип каждого свойства
Перегрузка инициализаторов
Ну что ж, класс мы написали. Теперь пришло время создать первых пользователей. Поле краткой информации — дополнительное, значит нам нужно перегрузить инициализатор, т.к. можно как написать что-то про игрока, так и оставить его тёмной личностью. Ну что ж, давайте напишем инициализаторы для обоих случаев:
Однако согласитесь, это не совсем рационально, ведь отличия в инициализации минимальные — про кого-то есть какая-то информация, а про кого-то — нет. Поэтому давайте напишем один инициализатор, а аргументу bio дадим значение по умолчанию.
Идентичность классов
Операторы идентичности
Поскольку класс является ссылочным типом, может возникнуть ситуация, когда один и тот же экземпляр класса обозначается несколькими переменными или константами. (Это невозможно для структур и перечислений, т.к. и те, и другие являются значимыми типами и копируются при присваивании и передаче в функцию в качестве параметров)
Предположим, что у нас есть список игроков, один из них — владелец замка. Нам необходимо добавить какие-то функции для игроков, не являющихся владельцами замка. Для этого давайте используем оператор идентичности ( === )
Оператор равенства ( == ) не работает при сравнении объектов классов, так что вам не следует бояться подобных ошибок.
В следующей части мы рассмотрим наследование классов и протоколы.
Подробно об инициализации. Часть 1/2
Подробно об инициализации. Часть 1/2
Из этого туториала, вы узнаете все тонкости создания инициализаторов для типов в Swift. В первой части вы начнете с основ, включая структуру инициализации, а во второй части вы узнаете об инициализации класса.
Перед тем как приступить к туториалу, вы уже должны быть знакомы с основами инициализации в Swift и свободно владеть такими понятиями, как опциональные типы, генерация (throwing) и обработка ошибок (error-handling), и объявление начальных значений для свойств хранения. Кроме того, убедитесь, что у вас установлен Xcode 7.3 или более поздняя версия.
Если вам нужно освежить в памяти основы, то пробегитесь по курсу или по документации.
Приступим
Давайте представим: это ваш первый день на новой работе в качестве инженера программного обеспечения в НАСА (Ого-го!). Вам была поставлена задача разработать модель данных, которая будет управлять последовательностью запуска первого пилотируемого полета на Марс, «Mars Unum». Конечно, первое, что вы сделаете, это убедите команду использовать Swift. Затем …
Откройте Xcode и создайте новый плейграунд под названием BlastOff. Можете выбрать любую платформу, так как код в этом туториале не зависит от платформы, а зависит только от фреймворка Foundation.
На протяжении всего туториала помните золотое правило: вы не можете использовать экземпляр до тех пор, пока он не будет полностью инициализирован. «Использование» экземпляра включает в себя доступ к свойствам, настройки свойств и вызов методов. Все, что мы разбираем в первой части, относится конкретно к структурам, если не указано иное.
Рассмотрим дефолтный инициализатор
Для того, чтобы начать моделирование последовательности запуска, в плейграунде объявим новую структуру под названием RocketConfiguration:
Под закрывающей фигурной скобкой определения RocketConfiguration, инициализируйте постоянный экземпляр athena9Heavy:
При этом используется default initializer для создания экземпляра athena9Heavy. В дефолтном инициализаторе за именем типа следуют пустые скобки. Вы можете использовать дефолтные инициализаторы, когда у ваших типов либо нет каких-либо свойств хранения, либо у всех типов свойств хранения есть начальные значения. Это справедливо и для структур и для классов.
Добавьте следующие три свойства хранения внутри определения структуры:
Обратите внимание на то, что дефолтный инициализатор продолжает работать. Код продолжает работать, потому что все свойства хранения имеют дефолтные значения. Это означает, что у дефолтного инициализатора не очень много работы, ведь вы предоставили все начальные значения!
А что насчет опциональных типов? Добавьте свойство хранения numberOfStageReuseLandingLegs к определению структуры:
Изменените numberOfStageReuseLandingLegs из переменной в константу:
Плейграунд выдает ошибку компиляции:
Вы не будете часто с этим сталкиваться, так как опциональные константы нужны редко. Чтобы исправить ошибку компилятора, присвойте дефолтное значение nil для numberOfStageReuseLandingLegs:
Ура! Компилятор снова счастлив, и инициализация проходит успешно. С такой постановкой у numberOfStageReuseLandingLegs никогда не будет значения не nil. Вы не сможете изменить его после инициализации, так как оно объявлено как константа.
Рассмотрим почленный инициализатор (Memberwise Initializer)
У ракеты, как правило, есть ступени, давайте займемся этим после моделирования. Объявите новую структуру под названием RocketStageConfiguration в нижней части плейграунда:
На этот раз, у вас есть три свойства хранения: propellantMass, liquidOxygenMass и nominalBurnTime без начальных значений.
Создайте экземпляр RocketStageConfiguration для первой ступени ракеты:
Ни одно из свойств хранения RocketStageConfiguration не имеет начального значения. Кроме того, нет инициализатора, реализованного для RocketStageConfiguration. Почему тогда не выходит ошибка компилятора? Структуры в Swift (и только структуры) автоматически генерируют memberwise initializer (почленный инициализатор). Это означает, что вы получаете готовый инициализатор для всех свойств хранения, у которых нет начальных значений. Это супер удобно, но тут есть несколько подводных камней.
Представьте, что вы представляете этот фрагмент для проверки кода и ваш руководитель команды разработчиков говорит вам, что все свойства должны быть упорядочены в алфавитном порядке.
Обновите RocketStageConfiguration для упорядочевания свойств хранения:
Что случилось? Вызов инициализатора stageOneConfiguaration больше не действует, так как порядок списка автоматических почленных инициализаторов отражает список свойств хранения. Будьте осторожны, при повторном упорядочивании свойств структуры, вы можете нарушить инициализацию экземпляра. К счастью, компилятор должен поймать ошибку, но однозначно за этим стоит проследить.
Отмените повторное упорядочение свойств хранения, чтобы плейграунд опять компилировался и запускался:
Теперь выходит другая ошибка компиляции:
Компиляция не проходит, потому что почленные инициализаторы обеспечивают параметры только для свойств хранения без дефолтных значений. В этом случае почленный инициализатор участвует только в пороховой массе и жидкой массе кислорода, так как дефолтное значение для времени запуска уже существует.
Удалите дефолтное значение nominalBurnTime, чтобы устранить ошибку компилятора:
Затем добавьте пользовательский инициализатор к определению структуры, которая обеспечивает дефолтное значение для времени запуска:
Обратите внимание, что ошибка компиляции теперь опять выходит на stageOneConfiguration:
Другими словами, Swift помогает вам вначале. Но как только вы добавляете свой собственный инициализатор, это предполагает, что вы хотите действовать самостоятельно.
Удалите аргумент nominalBurnTime из инициализации stageOneConfiguration:
Все снова работает! :]
Но что, если вам все еще нужен автоматический почленный инициализатор? Вы, конечно, можете написать эквивалентный инициализатор, но это большая работа. Вместо этого переместите пользовательский инициализатор в отдельное расширение (extension), прежде чем вы реализуете экземпляр.
Ваша структура теперь будет состоять из двух частей: основного определения и расширения с инициализатором двух параметров:
Обратите внимание, как stageOneConfiguration продолжает успешно инициализировать с двумя параметрами. Теперь снова добавьте параметр nominalBurnTime к инициализации stageOneConfiguration:
И это работает! Если основное определение структуры не включает в себя инициализаторов, Swift все равно будет автоматически генерировать почленные дефолтные инициализаторы. Затем вы можете добавить свои настройки с помощью расширений и получить лучшее из двух вариантов.
Реализация пользовательского инициализатора
Погода играет ключевую роль в запуске ракет, так что вам нужно это отразить в модели данных. Объявляется новую структуру под названием Weather:
У struct есть свойства хранения температуры в градусах Цельсия и скорости ветра в километрах в час.
Реализуйте пользовательский инициализатор для Weather, которая принимает температуру в градусах по Фаренгейту и скорость ветра в милях в час. Добавьте этот код ниже свойств хранения:
Определение пользовательского инициализатора очень схоже с определением метода, так как список аргументов инициализатора ведет себя точно так же, как список метода. Например, вы можете определить начальное значение аргумента для любого из параметров инициализатора.
Измените определение инициализатора на:
Теперь, если вы вызовете инициализатор без параметров, то получите некоторые начальные значения. В конце файла плейграунда создайте экземпляр Weather и проверьте его значения:
Круто, не так ли? Дефолтный инициализатор использует дефолтные значения, предоставленные пользовательским инициализатором. Реализация пользовательского инициализатора переводит значения в метрическую систему эквивалентов и сохраняет значения. При проверке значения свойств хранения на боковой панели плейграунда, вы получите правильные значения в градусах Цельсия (22.2222) и километрах в час (8.047).
Инициализатор обязан присваивать значение каждому свойству хранения, у которого нет дефолтного значения или вы получите ошибку компилятора. Помните, что опциональные переменные автоматически имеют дефолтное значение nil.
Затем измените currentWeather, чтобы использовать ваш пользовательский инициализатор с новыми значениями:
Как вы можете видеть, пользовательские значения работают точно так же в инициализаторе как и дефолтные значения. На боковой панели плейграунда теперь должны появиться 30.556 (градусов) и 3,219 (км/ч).
Вот так вы реализуете и вызываете пользовательский инициализатор. Ваша новая структура (Weather struct) готова внести свой вклад в миссию запуска человека на Марс. Отличная работа!
Избегаем дублирования с делегирующим инициализатором
Пора думать о том, как управлять ракетой. Ракетам нужна отменная система наведения для того, чтобы их полет был строго вертикальным. Объявите новую структуру под названием GuidanceSensorStatus со следующим кодом:
Эта структура содержит текущую и начальную угловую скорость ракеты относительно Z-оси (насколько она вращается). Структура также отслеживает, необходима ли ракете поправка, чтобы остаться на своей траектории.
Обычный инициализатор имеет важную логику преобразования градусов в минуту в радианы в минуту. Инициализатор также устанавливает начальное значение угловой скорости для сохранения относительных данных.
Вы довольный пишите код, когда приходят инженеры и говорят, что новая версия ракеты дает вам Int для needsCorrection вместо Bool. Инженеры говорят, что положительный интеджер должен интерпретироваться как true, в то время как нулевое и отрицательное значение следует интерпретировать как false. Ваша команда не готова изменить остальную часть кода, так как это изменение является частью будущей функции. Итак, как можно угодить инженерам запуска и в то же время сохранить определение структуры нетронутым?
Этот новый инициализатор принимает Int вместо Bool в качестве конечного параметра. Тем не менее свойство хранения needsCorrection еще Bool, то есть вы учли их требования.
После того, как вы написали этот код, что-то внутри подсказывает вам, что можно сделать еще лучше. Так много повторов остальной части кода инициализатора! И если будет ошибка в конверсии в радианы, то вам придется исправлять ее в нескольких местах — этого можно избежать. Здесь пригодится initializer delegation или делегирование инициализатора.
Замените инициализатор, который вы только что написали, следующим:
Делегируемый инициализатор полезен, когда вы хотите обеспечить инициализатор с альтернативным списком аргументов, но вы не хотите повторять логику вашего пользовательского инициализатора. Кроме того, использование делегируемого инициализатора помогает снизить объем кода, который вам нужно написать.
Для проверки инициализатора, создайте экземпляр переменной с именем guidanceStatus:
Плейграунд должен скомпилироваться и запуститься, и эти два значения, которые вы проверяли на свойства guidanceStatus, будут находиться на боковой панели.
Вот еще что: вас попросили предоставить другой инициализатор, который устанавливает по умолчанию needsCorrection на false. Это должно быть так же просто, как создание нового делегирующего инициализатора и установка начального значения свойства needsCorrection перед делегированием инициализации. Попробуйте добавить следующий инициализатор в структуру, и обратите внимание, что он не будет компилироваться.
Понимая это, удалите новый инициализатор и дайте аргументу needsCorrection основного инициализатора дефолтное значение false:
Обновите инициализацию guidanceStatus, удалив аргумент needsCorrection:
Init swift что это такое
Инициализация представляет процесс подготовки объекта класса, структуры или перечисления к использованию. Этот процесс может включать установку начальных значений для свойств класса.
Для создания объекта класса используется инициализатор. Каждый класс имеет инициализатор по умолчанию:
Выражение User() преставляет вызов инициализатора.
Важно, что хранимые свойства класса (то есть переменные и константы) должны быть инициализированы и иметь определенное значение ко времени создания объекта класса. В данном случае свойствам класса name и age напрямую присваиваются значения. Но также для инициализации свойств может использоваться инициализатор.
Для переопределения инициализатора в классе нам надо использовать ключевое слово init :
Стоит отметить, что так как переменным name и age не присваиваются начальные значения, а их инициализация производится в инициализаторе, то можно определить эти переменные как константы:
Фактически инициализатор представляет функцию, которая выполняет начальную инициализацию объекта.
При необходимости мы можем определять дополнительные инициализаторы:
И при создании объекта в данном случае используется второй инициализатор: var bob: User = User(name: «Bob», age: 34)
Инициализаторы могут определять значения по умолчанию для параметров. Так, пример выше мы могли бы сократить следующим образом:
Делегирование инициализации
Одни инициализаторы могут вызывать другие. Вызывающие инициализаторы должны быть определены с ключевым словом convenience :
Failable-инициализаторы
Специальная разновидность инициализаторов (Failable Initializer) позволяет возвратить значение nil, если в процессе инициализации объекта произошла какая-нибудь ошибка. Например:
Поскольку пользователь, представленный классом User, в принципе не может иметь возраст меньше нуля. Поэтому ситуация, когда для возраста передается число меньше нуля, может рассматриваться как ошибочная. И в этом случае мы как раз можем использовать failable-инициализатор.
Для определения такого инициализатора после слова init ставится знак вопроса, а в самом инициализаторе можно предусмотреть ситуацию, при которой он возвращает значение nil :
Возвращая nil, мы тем самым указываем, что мы не можем создать объект User по тем данным, которые переданы в инициализатор.
Либо мы можем напрямую работать с объектом User?:
Однако надо учитывать, что если мы передадим неправильные данные, то инициализатор возвратит nil, и получение значение окончится ошибкой. Поэтому в таких случаях перед использованием объекта мы можем проверять на наличие значения:
Документация
Инициализация
Инициализация
Экземпляры классовых типов также могут реализовывать деинициализаторы, которые проводят любую чистку прямо перед тем, как экземпляр класса будет освобожден. Для более подробной информации читайте Деинициализация.
Установка начальных значений для свойств хранения
Классы и структуры должны устанавливать начальные значения у всех свойств хранения во время создания класса или структуры. Свойства хранения не могут быть оставлены в неопределённом состоянии.
Вы можете установить начальное значение свойства внутри инициализатора или присвоить ему значение по умолчанию, как часть определения свойства. Эти действия будут описаны подробнее в следующих секциях.
Заметка
Когда вы присваиваете значение по умолчанию свойству хранения или устанавливаете исходное значение в инициализаторе, то значение устанавливается напрямую, без вызова наблюдателей.
Инициализаторы
Инициализаторы вызываются для создания нового экземпляра конкретного типа. В самой простой своей форме инициализатор работает как метод экземпляра без параметров, написанный с помощью ключевого слова init :
Пример ниже определяет новую структуру Fahrenheit для хранения температур, представленных в Фаренгейтах. Структура Fahrenheit имеет всего одно свойство, temperature типа Double :
Дефолтные значения свойств
Вы можете установить исходное значение свойства в инициализаторе, как показано выше. Так же вы можете указать значение свойства по умолчанию, как часть определения свойства. Вы указываете значение свойства по умолчанию, написав исходное значение свойства, когда оно определено.
Заметка
Если свойство каждый раз берет одно и то же исходное значение, то лучше указать это значение, в качестве значения по умолчанию, чем каждый раз устанавливать его в инициализаторе. Конечный результат такой же, но значение по умолчанию связывает инициализацию свойства ближе к своему объявлению. Так делают, чтобы оставить инициализаторы в более чистой и краткой форме, и это позволяет вам вывести тип свойства из его значения по умолчанию. Значения по умолчанию так же дают вам больше преимуществ для использования инициализаторов по умолчанию или наследования инициализатора, что описано подробнее далее в этой главе.
Настройка инициализации
Вы можете настроить процесс инициализации входными параметрами и опциональными типами свойств или присваиванием значений для постоянных свойств во время инициализации, что будет описано далее.
Параметры инициализации
Вы можете показать параметры инициализации как часть определения инициализатора, для определения типов и имен значений, которые настраивают процесс инициализации. Параметры инициализации имеют те же возможности и синтаксис как и параметры функции или метода.
Локальные и внешние имена параметров
Как и в случае с параметрами функций или методов, параметры инициализации могут иметь локальные имена для использования внутри тела инициализатора и внешние для использования при вызове инициализатора.
Однако инициализаторы не имеют своего имени до круглых скобок, как это имеют методы или функции. Поэтому имена и типы параметров инициализатора играют важную роль в определении того, какой инициализатор и где может быть использован. Из-за этого Swift предоставляет автоматические внешние имена для каждого параметра, если вы, конечно, не укажете свое внешнее имя.
Обратите внимание, что невозможно вызвать инициализатор без использования внешних имен. Внешние имена обязательно должны быть использованы в инициализаторе, если они определены, если пропустить их, то выскочит ошибка компиляции:
Параметры инициализатора без внешних имен
Если вы не хотите использовать внешние имена для параметров инициализации, напишите подчеркивание ( _ ) вместо явного указания внешнего имени для этого параметра, чтобы переопределить поведение по умолчанию.
Опциональные типы свойств
Следующий код определяет класс SurveyQuestion с опциональным типом String свойства response :
Присваивание значений постоянным свойствам во время инициализации
Вы можете присвоить значения постоянных (когда свойство константа) свойств в любой точке вашего процесса инициализации. Как только для свойства константы присваивается значение, оно не может быть далее изменено.
Заметка
В экземплярах класса постоянное свойство может быть изменено только во время инициализации класса, в котором оно представлено. Оно не может быть изменено подклассом.
Дефолтные инициализаторы
Swift предоставляет дефолтный инициализатор для любой структуры или базового класса, который имеет значение по умолчанию для всех его свойств и не имеет ни одного инициализатора. Дефолтный инициализатор просто создает новый экземпляр со всеми его свойствами с уже присвоенными значениями по умолчанию.
Почленные инициализаторы структурных типов
Структурные типы автоматически получают почленный инициализатор, если они не определяют своего пользовательского инициализатора. Это верно даже при условии, что хранимые свойства не имеют значений по умолчанию.
Структура Size автоматически получает init(width:height: ) почленный инициализатор, который вы можете использовать для инициализации Size экземпляра:
Делегирование инициализатора для типов значения
Инициализаторы могут вызывать другие инициализаторы для инициализации части экземпляра. Этот процесс называется как делегирование инициализатора. Он позволяет избегать дублирования кода в разных инициализаторах.
Правила того, как работает делегирование инициализатора и для каких форм делегирования это возможно, для типов значений и ссылочных типов разные. Типы значений (структуры и перечисления) не поддерживают наследование, так что их процесс делегирования инициализатора сравнительно прост, потому что они только могут делегировать другому инициализатору то, что предоставляют сами. Классы, однако, могут наследовать от других классов, как это описано в Наследование. Это значит, что у классов есть дополнительная ответственность за проверку наличия корректных значений у каждого унаследованного свойства хранения класса. Обязанности описаны в главе Наследование и инициализация класса.
Для типов значений вы используете self.init для ссылки на остальные инициализаторы одного и того же типа значения, когда вы пишите свои инициализаторы. Вы можете вызывать self.init из инициализатора.
Обратите внимание, что если вы определите пользовательский инициализатор для типов значений, то вы больше не будете иметь доступа к дефолтному инициализатору (или почленному инициализатору, если это структура) для этого типа. Такое ограничение предотвращает ситуацию, в которой настройка важного дополнения в более сложном инициализаторе может быть пропущена при случайном использовании автоматического инициализатора.
Заметка
Если вы хотите, чтобы ваш пользовательский тип значения имел возможность быть инициализированным дефолтным инициализатором или почленным инициализатором, или вашим пользовательским инициализатором, то вам нужно написать свой пользовательский инициализатор в расширении вашего типа, чем как часть реализации типа значения. Для более подробной информации смотрите Расширения.
Заметка
Наследование и инициализация класса
Всем свойствам класса, включая и те, что унаследованы у суперкласса должны быть присвоены начальные значения, во время их инициализации.
Swift определяет два вида инициализаторов классовых типов для проверки того, что все свойства получили какие-либо значения. Они известны как назначенные инициализаторы (конструкторы) или вспомогательные инициализаторы.
Назначенный и вспомогательный инициализатор
Назначенные инициализаторы в основном инициализаторы класса. Они предназначены для того, чтобы полностью инициализировать все свойства представленные классом и чтобы вызвать соответствующий инициализатор суперкласса для продолжения процесса инициализации цепочки наследований суперклассов.
Так сложилось, что классы чаще всего имеют очень мало назначенных инициализаторов, чаще всего бывает, что класс имеет всего один инициализатор. Назначенные инициализаторы объединяют в себе все точки, через которые проходит процесс инициализации и через которые процесс инициализации идет по цепочке в суперкласс.
Каждый класс должен иметь хотя бы один назначенный инициализатор. В некоторых случаях, это требование удовлетворяется наследованием одного или более назначенных инициализаторов от суперкласса.
Вспомогательные инициализаторы являются вторичными, поддерживающими инициализаторами для класса. Вы можете определить вспомогательный инициализатор для вызова назначенного инициализатора из того же класса, что и вспомогательный инициализатор с некоторыми параметрами назначенного инициализатора с установленными начальными значениями.
Вы не обязаны обеспечивать вспомогательные инициализаторы, если ваш класс не нуждается в них. Создавайте вспомогательный инициализатор всякий раз, когда это является наиболее рациональным путем общей инициализации и может сэкономить время и сделать саму инициализацию класса более чистой и краткой.
Синтаксис назначенных и вспомогательных инициализаторов
Назначенные инициализаторы для классов записываются точно так же как и простые инициализаторы для типов значений:
Делегирование инициализатора для классовых типов
Для простоты отношений между назначенными и вспомогательными инициализаторами, Swift использует следующие три правила для делегирования вызовов между инициализаторами:
Правило 1
Назначенный инициализатор должен вызывать назначенный инициализатор из суперкласса.
Правило 2
Вспомогательный инициализатор должен вызывать другой инициализатор из того же класса.
Правило 3
Вспомогательный инициализатор в конечном счете должен вызывать назначенный инициализатор.
Вот как можно просто это запомнить:
Вот как это правило выглядит в иллюстрированной форме:
Здесь, в суперклассе есть один назначенный инициализатор и два вспомогательных инициализатора. Один вспомогательный инициализатор вызывает другой вспомогательный инициализатор, который в свою очередь вызывает единственный назначенный инициализатор. Этот рисунок удовлетворяет правилам 2 и 3. Этот суперкласс сам по себе уже дальше не имеет суперкласса, так что первое правило здесь не применимо.
Подкласс на этом рисунке содержит два назначенных инициализатора и один вспомогательный инициализатор. Вспомогательный инициализатор должен вызвать одного из двух назначенных инициализаторов, потому что он может вызвать другой инициализатор того же класса. Это правило так же соответствует правилам 2 и 3. Оба назначенных инициализатора должны вызвать один назначенный инициализатор из суперкласса, для того чтобы соответствовать правилу 1.
Заметка
Эти правила никак не относятся к тому, как пользователи ваших классов создают экземпляры каждого класса. Любой инициализатор из схемы выше может быть использован для создания полностью инициализированного экземпляра класса, которому он принадлежит. Правила влияют лишь на то, как вы будете писать реализацию класса.
Схема ниже показывает более сложную иерархию из четырех классов. Она показывает как назначенные инициализаторы работают в качестве точек прохождения инициализации класса, упрощая внутренние взаимоотношения среди цепочки классов:
Двухфазная инициализация
Инициализация класса в Swift является двухфазным процессом. На первой фазе каждое свойство хранения получает начальное значение от класса, в котором оно представлено. Как только первоначальные значения для свойств хранения были определены, начинается вторая фаза, и каждому классу предоставляется возможность изменить свои свойства еще до того как будет считаться, что созданный экземпляр можно использовать.
Использование двухфазного процесса инициализации делает инициализацию безопасной, в то же время обеспечивая полную гибкость классов в классовой иерархии. Двухфазная инициализация предотвращает доступ к значениям свойств до того, как они будут инициализированы и не допускает случайную установку значения свойства другим инициализатором.
Заметка
Компилятор Swift проводит четыре полезные проверки безопасности для подтверждения того, что ваша двухфазная инициализация прошла без ошибок:
Проверка 1. Назначенный инициализатор должен убедиться в том, что все свойства представленные его классом инициализированы до того, как он делегирует наверх, в инициализатор суперкласса.
Как было сказано выше, память для объекта считается полностью инициализированной только для полностью инициализированного объекта, где все значения хранимых свойств известны. Для того чтобы удовлетворить этому правилу, назначенный инициализатор должен убедиться, что все его собственные свойства инициализированы до того, как будут переданы вверх по цепочке.
Проверка 2. Назначенный инициализатор должен делегировать суперклассу инициализатор до присваивания значений унаследованным свойствам. Если этого сделано не будет, то новое значение, которое присвоит назначенный инициализатор будет переписано суперклассом, как часть инициализации суперкласса.
Проверка 3. Вспомогательный инициализатор должен делегировать другому инициализатору до того, как будут присвоены значения любым свойствам (включая свойства определенные тем же классом). Если этого сделано не будет, то новое значение, которое присваивает вспомогательный инициализатор, будет перезаписано его собственным назначенным инициализатором класса.
Проверка 4. Инициализатор не может вызывать методы экземпляра, читать значения любого свойства экземпляра или ссылаться на self как на значение до тех пор, пока не будет закончена первая фаза инициализации.
Экземпляр класса является не совсем корректным до тех пор, пока не закончится первая фаза. К свойствам можно получить доступ и можно вызывать методы только тогда, как стало известно, что экземпляр валиден (корректен) к концу первой фазы.
Вот как проходит двухфазная инициализация, основанная на четырех проверках(описанные выше):
Фаза первая
Фаза вторая
Вот как выглядит первая фаза для гипотетического подкласса и суперкласса:
В этом примере инициализация начинается с вызова вспомогательного инициализатора подкласса. Вспомогательный инициализатор пока не может изменять каких-либо свойств. Он делегирует назначенному инициализатору по тому же классу, где и он сам.
Назначенный инициализатор убеждается, что все свойства, подкласса имеют значения, как указано в проверке 1. После этого он вызывает назначенный инициализатор своего суперкласса для продолжения инициализации.
Назначенный инициализатор суперкласса проверяет наличие значений у всех свойств суперкласса. Далее нет продолжения цепочки наверх, к другому суперклассу, так что нет дальнейшей нужды в делегировании.
Сразу после того как все свойства суперкласса получают начальные значения, память считается полностью инициализированной, Фаза 1 завершается.
Вот как выглядит Фаза 2:
Назначенный инициализатор суперкласса только теперь получает возможность менять дальнейшие экземпляры(хотя и не обязан).
Как только назначенный инициализатор суперкласса заканчивает работу, получает возможность вносить изменения назначенный инициализатор подкласса (хотя он так же и не обязан это делать).
Наконец, как только заканчивает работу назначенный инициализатор подкласса, то возможность вносить изменения получает вспомогательный инициализатор, который изначально был вызван.
То есть если рассмотреть весь процесс отдаленно, то он получается, как бы, идет вверх, а потом спускается вниз.
Наследование и переопределение инициализатора
В отличии от подклассов в Objective-C, подклассы в Swift не наследуют инициализаторов их суперклассов по умолчанию. Такой подход в Swift предотвращает ситуации, когда простой инициализатор суперкласса наследуется более специфичным подклассом, а потом используется для создания экземпляра подкласса, который не полностью или не правильно инициализирован.
Заметка
Инициализаторы суперкласса наследуются в определенных обстоятельствах, но только когда это безопасно и когда это имеет смысл делать. Далее мы это разберем.
Если вы хотите, чтобы у вашего подкласса были один или более инициализаторов его суперклассов, вы можете сделать свою реализацию этих инициализаторов внутри подкласса.
Когда вы пишите инициализатор подкласса, который совпадает с назначенным инициализатором суперкласса, вы фактически переопределяете назначенный инициализатор. Таким образом вы должны писать модификатор override перед определением инициализатора подкласса. Это верно даже если вы переопределяете автоматически предоставляемый инициализатор, как описано в Дефолтные инициализаторы.
Так же как и переопределенные свойства, методы или индексы, присутствие модификатора override подсказывает Swift проверить то, что суперкласс имеет совпадающий назначенный инициализатор, который должен быть переписан, и проверить параметры вашего переопределяющего инициализатора, чтобы они были определены так как и предполагалось.
Заметка
Класс Vehicle предоставляет значение по умолчанию для его единственного свойства, и не имеет никаких собственных пользовательских инициализаторов. И в результате он автоматически получает дефолтный инициализатор, как описано в главе Дефолтные инициализаторы. Дефолтный инициализатор (когда доступен) всегда является назначенным инициализатором для класса и может быть использован для создания нового экземпляра класса Vehicle с numberOfWheels равным 0 :
Следующий пример определяет подкласс Bicycle суперкласса Vehicle :
Заметка
Подклассы могут менять унаследованные переменные свойства в процессе инициализации, но нельзя менять неизменяемые унаследованные свойства.
Автоматическое наследование инициализатора
Как было сказано ранее, подклассы не наследуют инициализаторы суперкласса по умолчанию. Однако инициализаторы суперкласса автоматически наследуются, если есть для того специальные условия. На практике это значит, что во многих случаях вам не нужно писать переопределения инициализатора, так как он может наследовать инициализаторы суперкласса с минимальными усилиями, но только когда это безопасно.
Допуская, что вы предоставляете значения по умолчанию любому новому свойству, представленному в подклассе, то применяются два правила:
Правило 1. Если ваш подкласс не определяет ни одного назначенного инициализатора, он автоматически наследует все назначенные инициализаторы суперкласса.
Правило 2. Если у вашего класса есть реализация всех назначенных инициализаторов его суперкласса, либо они были унаследованы как по правилу 1 или же предоставлены как часть пользовательской реализации определения подкласса, то тогда этот подкласс автоматически наследует все вспомогательные инициализаторы суперкласса.
Эти правила применимы даже если ваш подкласс позже добавляет вспомогательные инициализаторы.
Заметка
Подкласс может реализовать назначенный инициализатор суперкласса как вспомогательный инициализатор подкласса в качестве части удовлетворяющей правилу 2.
Назначенные и вспомогательные инициализаторы в действии
Схема ниже показывает цепочку работы инициализаторов в классе Food :
Схема ниже показывает цепочку инициализаторов для класса RecipeIngredient :
Даже если RecipeIngredient представляет инициализатор init(name: String) как вспомогательный инициализатор, то RecipeIngredient тем не менее проводит реализацию всех назначенных инициализаторов своего суперкласса. Таким образом RecipeIngredient автоматически наследует все свойства вспомогательных инициализаторов своего суперкласса тоже.
Все три инициализатора могут быть использованы для создания новых RecipeIngredient экземпляров:
Заметка
Так как он предоставляет исходные значения для всех свойств, которые он представляет и не определяет никаких своих инициализаторов, то ShoppingListItem автоматически наследует все назначенные и вспомогательные инициализаторы из своего суперкласса.
Схема ниже отображает общую цепочку инициализаций для всех трех классов:
Вы можете использовать все три унаследованных инициализатора для создания нового экземпляра ShoppingListItem :
Проваливающиеся инициализаторы
Иногда бывает нужно определить класс, структуру или перечисление, инициализация которого может не сработать, провалиться. Такое неисполнение может быть вызвано некорректными значениями параметров или отсутствием требуемого внешнего источника данных или еще какое-нибудь обстоятельство, которое может не позволить завершить инициализацию успешно.
Для того чтобы справиться с условиями инициализации, которые могут провалиться, определите один или несколько проваливающихся инициализаторов как часть определения класса, структуры или перечисления. Вы можете написать проваливающийся инициализатор поместив вопросительный знак после ключевого слова init ( init? ).
Заметка
Вы не можете определить проваливающийся инициализатор и обычные инициализаторы с одними и теми же именами и типами параметров.
Проваливающийся инициализатор создает опциональное значение типа, который он инициализирует. Вы пишите return nil внутри проваливающегося инициализатора для индикации точки, где инициализация может провалиться.
Заметка
Строго говоря, инициализаторы не возвращают значений. Их роль заключается в том, что они проверяют, что self полностью и корректно инициализирован, до того, как инициализация закончится. Несмотря на то, что вы пишите return nil для указания неудачи инициализации, вы не пишите слово return в случае, если инициализация прошла успешно.
Вы можете использовать этот проваливающий инициализатор для попытки инициализировать новый экземпляр структуры Animal и проверить успешно ли прошла инициализация:
Если вы передаете пустую строку в параметр species проваливающегося инициализатора, то инициализатор вызывает сбой инициализации:
Заметка
Проваливающиеся инициализаторы для перечислений
Вы можете использовать проваливающийся инициализатор для выбора подходящего члена перечисления основываясь на одном или более параметров. Инициализатор может провалиться, если предоставленные параметры не будут соответствовать подходящему члену перечисления.
Вы можете использовать этот проваливающийся инициализатор для выбора соответствующего члена из трех возможных состояний и вызвать провал инициализации, если параметр не соответствует этим состояниям:
Проваливающиеся инициализаторы для перечислений с начальными значениями
Вы можете переписать пример TemperatureUnit из примера выше для использования начальных значений типа Character и использовать инициализатор init?(rawValue: ) :
Распространение проваливающегося инициализатора
Проваливающийся инициализатор класса, структуры, перечисления может быть делегирован к другому проваливающемуся инициализатору из того же класса, структуры, перечисления. Аналогично проваливающийся инициализатор подкласса может быть делегирован наверх в проваливающийся инициализатор суперкласса.
В любом случае, если вы делегируете другому инициализатору, который проваливает инициализацию то и весь процесс инициализации проваливается немедленно за ним, и далее никакой код инициализации уже не исполняется.
Заметка
Проваливающийся инициализатор может также делегировать к непроваливающемуся инициализатору. Используя такой подход, вам следует добавить потенциальное состояние провала в существующий процесс инициализации, который в противном случае не провалится.
Если вы создаете экземпляр CartItem с name не равной пустой строке и quantity равному 1 или более, то инициализация проходит успешно:
Аналогично, если вы попытаетесь создать экземпляр CartItem с name равным пустой строке, то инициализатор суперкласса Product вызовет неудачу инициализации:
Переопределение проваливающегося инициализатора
Вы можете переопределить проваливающийся инициализатор суперкласса в подклассе, так же как любой другой инициализатор. Или вы можете переопределить проваливающий инициализатор суперкласса непроваливающимся инициализатором подкласса. Это позволяет вам определить подкласс, для которого инициализация не может провалиться, даже когда инициализация суперкласса позволяет это сделать.
Заметка
Вы можете переопределить проваливающийся инициализатор непроваливающимся инициализатором, но не наоборот.
В этом случае инициализатор суперкласса init(name: ) каждый раз будет вызывать ошибку исполнения, если в него будет передана пустая строка. Однако, так как этот инициализатор теперь имеет строковую константу, то этот инициализатор больше не провалится, то есть ошибки исполнения больше не будет.
Проваливающийся инициализатор init!
Обычно вы определяете проваливающийся инициализатор, который создает опциональный экземпляр соответствующего типа путем размещения знака вопроса после ключевого слова init ( init? ). Альтернативно, вы можете определить проваливающийся инициализатор, который создает экземпляр неявно извлекаемого опционала соответствующего типа. Сделать это можно, если вместо вопросительного знака поставить восклицательный знак после ключевого слова init ( init! ).
Требуемые инициализаторы
Напишите required перед определением инициализатора класса, если вы хотите, чтобы каждый подкласс этого класса был обязан реализовывать этот инициализатор:
Заметка
Вы не должны обеспечивать явную реализацию требуемого инициализатора, если вы можете удовлетворить требование унаследованным инициализатором.
Начальное значение свойства в виде функции или замыкания
Если начальное значение свойства требует какой-то настройки или структуризации, то вы можете использовать замыкание или глобальную функцию, которая будет предоставлять значение для этого свойства. Как только создается новый экземпляр, вызывается функция или замыкание, которая возвращает значение, которое присваивается в качестве начального значения свойства.
Эти виды замыканий или функций обычно создают временное значение того же типа, что и свойство, используя эту величину для отображения желаемого начального состояния, затем возвращают ее в качестве начального значения свойства.
Ниже приведена схема того, как замыкание может предоставлять начальное значение свойству:
Обратите внимание, что после закрывающей фигурной скобки замыкания идут пустая пара круглых скобок. Это означает, что нужно исполнить это замыкание немедленно. Если вы пропустите эти скобки, то вы присваиваете само значение замыкания свойству, а не возвращаете значения замыкания.
Заметка
Если вы используете замыкание для инициализации свойства, помните, что остальная часть экземпляра еще не инициализирована, на тот момент когда исполняется замыкание. Это значит, что вы не можете получить доступ к значениям других свойств из вашего замыкания, даже если эти свойства имеют начальное значение. Вы так же не можете использовать неявное свойство self и не можете вызвать какой-либо метод вашего экземпляра.
Массив boardColors инициализирован с помощью замыкания для установки значений цветов:
Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.