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

Вы реализуете этот процесс инициализации , определяя инициализаторы , которые похожи на специальные методы, которые могут быть вызваны для создания нового экземпляра определенного типа. В отличие от инициализаторов Objective C, инициализаторы Swift не возвращают значение. Их основная задача - обеспечить правильную инициализацию новых экземпляров типа перед их первым использованием.

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

Установка начальных значений для сохраненных свойств

Классы и структуры должны установить для всех своих сохраненных свойств соответствующее начальное значение к моменту создания экземпляра этого класса или структуры. Сохраненные свойства нельзя оставлять в неопределенном состоянии.

Вы можете установить начальное значение для хранимого свойства в инициализаторе или назначив значение свойства по умолчанию как часть определения свойства. Эти действия описаны в следующих разделах.

ЗАМЕТКА

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

Инициализаторы

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

init() {

// perform some initialization here

}

В приведенном ниже примере определяется новая структура, призванная Fahrenheitхранить температуры, выраженные в шкале Фаренгейта. FahrenheitСтруктура имеет одно свойство, хранимую temperature, который имеет тип Double:

struct Fahrenheit {

var temperature: Double

init() {

temperature = 32.0

}

}

var f = Fahrenheit()

print("The default temperature is \(f.temperature)° Fahrenheit")

// Prints "The default temperature is 32.0° Fahrenheit"

Структура определяет один инициализатор initбез параметров, который инициализирует сохраненную температуру значением 32.0(точка замерзания воды в градусах Фаренгейта).

Значения свойств по умолчанию

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

ЗАМЕТКА

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

Вы можете написать Fahrenheitструктуру сверху в более простой форме, указав значение по умолчанию для его temperatureсвойства в тот момент, когда свойство объявлено:

struct Fahrenheit {

var temperature = 32.0

}

Настройка инициализации

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

Параметры инициализации

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

В следующем примере определяется структура с названием Celsius, которая хранит температуры, выраженные в градусах Цельсия. CelsiusСтруктура реализует два пользовательских инициализаторов называемых init(fromFahrenheit:)и init(fromKelvin:), которые инициализируют новый экземпляр структуры со значением из другой температурной шкалы:

struct Celsius {

var temperatureInCelsius: Double

init(fromFahrenheit fahrenheit: Double) {

temperatureInCelsius = (fahrenheit - 32.0) / 1.8

}

init(fromKelvin kelvin: Double) {

temperatureInCelsius = kelvin - 273.15

}

}

let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)

// boilingPointOfWater.temperatureInCelsius is 100.0

let freezingPointOfWater = Celsius(fromKelvin: 273.15)

// freezingPointOfWater.temperatureInCelsius is 0.0

Первый инициализатор имеет единственный параметр инициализации с меткой аргумента fromFahrenheitи именем параметра fahrenheit. Второй инициализатор имеет один параметр инициализации с меткой аргумента fromKelvinи именем параметра kelvin. Оба инициализатора преобразуют свой единственный аргумент в соответствующее значение в градусах Цельсия и сохраняют это значение в вызываемом свойстве temperatureInCelsius.

Имена параметров и метки аргументов

Как и в случае параметров функций и методов, параметры инициализации могут иметь как имя параметра для использования в теле инициализатора, так и метку аргумента для использования при вызове инициализатора.

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

Следующий пример определяет структуру , называемую Color, с тремя постоянными свойствами называемых redgreenи blue. Эти свойства хранят значение между 0.0и, 1.0чтобы указать количество красного, зеленого и синего в цвете.

Colorобеспечивает инициализатор с тремя соответственно именованными параметрами типа Doubleдля его красного, зеленого и синего компонентов. Colorтакже предоставляет второй инициализатор с одним whiteпараметром, который используется для обеспечения одинакового значения для всех трех цветовых компонентов.

struct Color {

let red, green, blue: Double

init(red: Double, green: Double, blue: Double) {

self.red = red

self.green = green

self.blue = blue

}

init(white: Double) {

red = white

green = white

blue = white

}

}

Оба инициализатора могут использоваться для создания нового Colorэкземпляра, предоставляя именованные значения для каждого параметра инициализатора:

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)

let halfGray = Color(white: 0.5)

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

let veryGreen = Color(0.0, 1.0, 0.0)

// this reports a compile-time error - argument labels are required

Параметры инициализатора без меток аргумента

Если вы не хотите использовать метку аргумента для параметра инициализатора, напишите подчеркивание ( _) вместо явной метки аргумента для этого параметра, чтобы переопределить поведение по умолчанию.

Вот расширенная версия Celsiusпримера из Параметры инициализации выше, с дополнительным инициализатором для создания нового Celsiusэкземпляра из Doubleзначения, которое уже находится в шкале Цельсия:

struct Celsius {

var temperatureInCelsius: Double

init(fromFahrenheit fahrenheit: Double) {

temperatureInCelsius = (fahrenheit - 32.0) / 1.8

}

init(fromKelvin kelvin: Double) {

temperatureInCelsius = kelvin - 273.15

}

init(_ celsius: Double) {

temperatureInCelsius = celsius

}

}

let bodyTemperature = Celsius(37.0)

// bodyTemperature.temperatureInCelsius is 37.0

Вызов инициализатора Celsius(37.0)понятен по своему намерению без необходимости метки аргумента. Поэтому целесообразно записать этот инициализатор так, чтобы он мог вызываться путем предоставления неназванного значения.init(_ celsius: Double)Double

Дополнительные типы недвижимости

Если ваш пользовательский тип имеет хранимое свойство, которое логически может иметь «нет значения» - возможно, из-за того, что его значение не может быть установлено во время инициализации, или потому, что ему разрешено иметь «нет значения» в какой-то более поздний момент - объявите свойство с помощью необязательный тип. Свойства необязательного типа автоматически инициализируются значением nil, указывающим, что свойство преднамеренно предназначено, чтобы иметь значение «еще нет» во время инициализации.

В следующем примере определяется вызываемый класс SurveyQuestionс необязательным Stringсвойством под названием response:

class SurveyQuestion {

var text: String

var response: String?

init(text: String) {

self.text = text

}

func ask() {

print(text)

}

}

let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")

cheeseQuestion.ask()

// Prints "Do you like cheese?"

cheeseQuestion.response = "Yes, I do like cheese."

Ответ на вопрос опроса не может быть известен до тех пор, пока он не будет задан, поэтому responseсвойство объявляется с типом String?или «необязательно String». При инициализации nilнового экземпляра ему автоматически присваивается значение по умолчанию , означающее «пока нет строки» SurveyQuestion.

Назначение постоянных свойств во время инициализации

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

ЗАМЕТКА

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

Вы можете изменить приведенный SurveyQuestionвыше пример, чтобы использовать свойство константы, а не свойство переменной для textсвойства вопроса, чтобы указать, что вопрос не изменяется после создания экземпляра SurveyQuestion. Даже если textсвойство теперь является константой, оно все равно может быть установлено в инициализаторе класса:

class SurveyQuestion {

let text: String

var response: String?

init(text: String) {

self.text = text

}

func ask() {

print(text)

}

}

let beetsQuestion = SurveyQuestion(text: "How about beets?")

beetsQuestion.ask()

// Prints "How about beets?"

beetsQuestion.response = "I also like beets. (But not with cheese.)"

Инициализаторы по умолчанию

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

В этом примере определяется класс с именем ShoppingListItem, который инкапсулирует имя, количество и состояние покупки предмета в списке покупок:

class ShoppingListItem {

var name: String?

var quantity = 1

var purchased = false

}

var item = ShoppingListItem()

Поскольку все свойства ShoppingListItemкласса имеют значения по умолчанию и поскольку это базовый класс без суперкласса, ShoppingListItemавтоматически получает реализацию инициализатора по умолчанию, которая создает новый экземпляр со всеми его свойствами, установленными в их значения по умолчанию. (Это nameнеобязательное Stringсвойство, поэтому оно автоматически получает значение по умолчанию nil, даже если это значение не записано в коде.) В приведенном выше примере используется инициализатор по умолчанию для ShoppingListItemкласса, чтобы создать новый экземпляр класса с инициализатором. синтаксис, записанный как ShoppingListItem(), и назначает этот новый экземпляр переменной с именем item.

Поэлементные инициализаторы для типов структуры

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

Поэлементный инициализатор - это сокращенный способ инициализации свойств элементов новых экземпляров структуры. Начальные значения свойств нового экземпляра могут быть переданы в элементный инициализатор по имени.

В приведенном ниже примере определяется структура Sizeс двумя свойствами widthи height. Оба свойства имеют тип Double, назначая значение по умолчанию 0.0.

SizeСтруктура автоматически получает init(width:height:)почленно инициализатор, который можно использовать для инициализации нового Sizeэкземпляра:

struct Size {

var width = 0.0, height = 0.0

}

let twoByTwo = Size(width: 2.0, height: 2.0)

Когда вы вызываете инициализатор по элементам, вы можете опустить значения для любых свойств, которые имеют значения по умолчанию. В приведенном выше примере Sizeструктура имеет значение по умолчанию как для своих, так heightи для widthсвойств. Вы можете опустить либо свойство, либо оба свойства, и инициализатор использует значение по умолчанию для всего, что вы пропускаете, например:

let zeroByTwo = Size(height: 2.0)

print(zeroByTwo.width, zeroByTwo.height)

// Prints "0.0 2.0"



let zeroByZero = Size()

print(zeroByZero.width, zeroByZero.height)

// Prints "0.0 0.0"

Делегирование инициализатора для типов значений

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

Правила того, как работает делегирование инициализатора и какие формы делегирования разрешены, различаются для типов значений и типов классов. Типы значений (структуры и перечисления) не поддерживают наследование, поэтому их процесс делегирования инициализатора относительно прост, поскольку они могут делегировать только другому инициализатору, который они предоставляют сами. Классы, однако, могут наследоваться от других классов, как описано в Inheritance . Это означает, что у классов есть дополнительные обязанности по обеспечению того, чтобы всем хранимым свойствам, которые они наследуют, было присвоено подходящее значение во время инициализации. Эти обязанности описаны в разделе «Наследование и инициализация класса» ниже.

Для типов значений вы используете self.initссылки на другие инициализаторы из того же типа значения при написании ваших собственных пользовательских инициализаторов. Вы можете звонить self.initтолько из инициализатора.

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

ЗАМЕТКА

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

В следующем примере определяется пользовательская Rectструктура для представления геометрического прямоугольника. В примере требуются две вспомогательные структуры с именем Sizeand Point, каждая из которых предоставляет значения по умолчанию 0.0для всех своих свойств:

struct Size {

var width = 0.0, height = 0.0

}

struct Point {

var x = 0.0, y = 0.0

}

Вы можете инициализировать приведенную Rectниже структуру одним из трех способов - используя значения по умолчанию, инициализированные нулем originи sizeзначения свойств, указав конкретную исходную точку и размер или указав конкретную центральную точку и размер. Эти параметры инициализации представлены тремя пользовательскими инициализаторами, которые являются частью определения Rectструктуры:

struct Rect {

var origin = Point()

var size = Size()

init() {}

init(origin: Point, size: Size) {

self.origin = origin

self.size = size

}

init(center: Point, size: Size) {

let originX = center.x - (size.width / 2)

let originY = center.y - (size.height / 2)

self.init(origin: Point(x: originX, y: originY), size: size)

}

}

Первый Rectинициализатор, init()функционально такой же, как и инициализатор по умолчанию, который структура получила бы, если бы у нее не было своих собственных пользовательских инициализаторов. Этот инициализатор имеет пустое тело, представленное пустой парой фигурных скобок {}. Вызов этого инициализатору возвращает Rectэкземпляр которого originи sizeсвойства как инициализируются со значениями по умолчанию и из их определений свойств:Point(x: 0.0, y: 0.0)Size(width: 0.0, height: 0.0)

let basicRect = Rect()

// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)

Второй Rectинициализатор, init(origin:size:)функционально такой же, как и элементный инициализатор, который структура получила бы, если бы у нее не было своих собственных пользовательских инициализаторов. Этот инициализатор просто присваивает значения аргумента originand sizeсоответствующим хранимым свойствам:

let originRect = Rect(origin: Point(x: 2.0, y: 2.0),

size: Size(width: 5.0, height: 5.0))

// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)

Третий Rectинициализатор init(center:size:)немного сложнее. Он начинается с вычисления соответствующей исходной точки на основе centerточки и sizeзначения. Затем он вызывает (или делегирует ) init(origin:size:)инициализатор, который сохраняет новые значения источника и размера в соответствующих свойствах:

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),

size: Size(width: 3.0, height: 3.0))

// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

init(center:size:)Инициализатор мог бы назначены новые значения originи sizeдля самых соответствующих свойств. Тем не менее, init(center:size:)инициализатору удобнее (и понятнее) использовать преимущества существующего инициализатора, который уже обеспечивает именно эту функциональность.

ЗАМЕТКА

Для альтернативного способа написания этого примера, не определяя сами init()и init(origin:size:)инициализаторы.

Наследование и инициализация класса

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

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

Назначенные инициализаторы и удобные инициализаторы

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

Классы, как правило, имеют очень мало обозначенных инициализаторов, и для класса довольно свойственно иметь только один. Обозначенные инициализаторы являются точками «воронки», через которые происходит инициализация и через которые процесс инициализации продолжается вверх по цепочке суперкласса.

Каждый класс должен иметь хотя бы один назначенный инициализатор. В некоторых случаях это требование удовлетворяется путем наследования одного или нескольких назначенных инициализаторов из суперкласса, как описано в разделе «Автоматическое наследование инициализатора» ниже.

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

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

Синтаксис для назначенных и удобных инициализаторов

Назначенные инициализаторы для классов пишутся так же, как и простые инициализаторы для типов значений:

init(parameters) {

statements

}

Удобные инициализаторы написаны в том же стиле, но с convenienceмодификатором, помещенным перед initключевым словом, разделенным пробелом:

convenience init(parameters) {

statements

}

Делегирование инициализатора для типов классов

Чтобы упростить отношения между назначенными и удобными инициализаторами, Swift применяет следующие три правила для вызовов делегирования между инициализаторами:

Правило 1

Назначенный инициализатор должен вызывать указанный инициализатор из своего непосредственного суперкласса.

Правило 2

Удобный инициализатор должен вызывать другой инициализатор из того же класса.

Правило 3

Удобный инициализатор должен в конечном счете вызвать назначенный инициализатор.

Простой способ запомнить это:

  • Назначенные Инициализаторы всегда должны делегировать вверх .
  • Инициализаторы удобства всегда должны делегироваться через .

Здесь суперкласс имеет один назначенный инициализатор и два вспомогательных инициализатора. Один удобный инициализатор вызывает другой удобный инициализатор, который, в свою очередь, вызывает единственный назначенный инициализатор. Это удовлетворяет правилам 2 и 3 сверху. Суперкласс сам по себе не имеет дополнительного суперкласса, и поэтому правило 1 не применяется.

 

Подкласс на этом рисунке имеет два назначенных инициализатора и один удобный инициализатор. Удобный инициализатор должен вызывать один из двух назначенных инициализаторов, потому что он может вызывать только другой инициализатор из того же класса. Это удовлетворяет правилам 2 и 3 сверху. Оба назначенных инициализатора должны вызывать один назначенный инициализатор из суперкласса, чтобы удовлетворить правило 1 сверху.

ЗАМЕТКА

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

На рисунке ниже показана более сложная иерархия классов для четырех классов. Он иллюстрирует, как обозначенные инициализаторы в этой иерархии действуют как «воронка» точек для инициализации класса, упрощая взаимосвязи между классами в цепочке.

Двухфазная инициализация

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

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

ЗАМЕТКА

Процесс двухфазной инициализации Свифта аналогичен инициализации в Objective-C. Основное различие заключается в том, что во время фазы 1 Objective-C назначает нулевое или нулевое значение (например, 0или nil) каждому свойству. Поток инициализации Свифта является более гибким в том , что она позволяет устанавливать пользовательские начальные значения, и может справиться с типами , для которых 0или nilне является допустимым значением по умолчанию.

Компилятор Swift выполняет четыре полезные проверки безопасности, чтобы убедиться, что двухфазная инициализация завершена без ошибок:

Проверка безопасности 1

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

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

Проверка безопасности 2

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

Проверка безопасности 3

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

Проверка безопасности 4

Инициализатор не может вызывать какие-либо методы экземпляра, считывать значения любых свойств экземпляра или называть их selfзначением до тех пор, пока не завершится первый этап инициализации.

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

Вот как работает двухфазная инициализация, основанная на четырех проверках безопасности выше:

Фаза 1

  • Обозначенный или удобный инициализатор вызывается для класса.
  • Память для нового экземпляра этого класса выделена. Память еще не инициализирована.
  • Назначенный инициализатор для этого класса подтверждает, что все сохраненные свойства, представленные этим классом, имеют значение. Память для этих сохраненных свойств теперь инициализирована.
  • Назначенный инициализатор передает инициализатору суперкласса для выполнения той же задачи для своих собственных сохраненных свойств.
  • Это продолжает цепочку наследования классов, пока не будет достигнут верх цепочки.
  • Как только вершина цепочки достигнута, и последний класс в цепочке гарантирует, что все его сохраненные свойства имеют значение, память экземпляра считается полностью инициализированной, и фаза 1 завершена.

Фаза 2

  • Возвращаясь к вершине цепочки, каждый назначенный инициализатор в цепочке имеет возможность дополнительно настроить экземпляр. Инициализаторы теперь могут получить доступ selfи могут изменять его свойства, вызывать методы его экземпляра и т. Д.
  • Наконец, любые удобные инициализаторы в цепочке имеют возможность настраивать экземпляр и работать с ним self.

В этом примере инициализация начинается с вызова удобного инициализатора в подклассе. Этот удобный инициализатор еще не может изменять какие-либо свойства. Он делегирует назначенному инициализатору из того же класса.

 

Назначенный инициализатор гарантирует, что все свойства подкласса имеют значение, согласно проверке безопасности 1. Затем он вызывает назначенный инициализатор в своем суперклассе, чтобы продолжить инициализацию по цепочке.

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

Как только все свойства суперкласса имеют начальное значение, его память считается полностью инициализированной, и фаза 1 завершается.

Назначенный инициализатор суперкласса теперь имеет возможность дополнительно настраивать экземпляр (хотя это и не обязательно).

 

Как только назначенный инициализатор суперкласса закончен, назначенный инициализатор подкласса может выполнить дополнительную настройку (хотя, опять же, это не обязательно).

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

Инициализация наследования и переопределение

В отличие от подклассов в Objective-C, подклассы Swift по умолчанию не наследуют свои инициализаторы суперкласса. Подход Swift предотвращает ситуацию, в которой простой инициализатор из суперкласса наследуется более специализированным подклассом и используется для создания нового экземпляра подкласса, который не полностью или правильно инициализирован.

ЗАМЕТКА

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

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

Когда вы пишете подкласс инициализатору, соответствующий суперкласс назначенногоинициализатор, вы фактически предоставляя переопределение этого назначенного инициализатора. Следовательно, вы должны написать overrideмодификатор до определения инициализатора подкласса. Это верно, даже если вы переопределяете автоматически предоставленный инициализатор по умолчанию, как описано в Инициализаторах по умолчанию .

Как и в случае с переопределенным свойством, методом или индексом, наличие overrideмодификатора заставляет Swift проверить, что суперкласс имеет соответствующий назначенный инициализатор для переопределения, и проверяет, что параметры для вашего переопределенного инициализатора были указаны как и предполагалось.

ЗАМЕТКА

Вы всегда пишете overrideмодификатор при переопределении инициализатора, назначенного суперклассом, даже если реализация инициализатора вашего подкласса является вспомогательным инициализатором.

И наоборот, если вы пишете инициализатор подкласса, который соответствует удобному инициализатору суперкласса , этот вспомогательный инициализатор суперкласса никогда не может быть вызван напрямую вашим подклассом согласно правилам, описанным выше в Делегировании инициализатора для типов классов . Следовательно, ваш подкласс (строго говоря) не обеспечивает переопределение инициализатора суперкласса. В результате вы не пишете overrideмодификатор при предоставлении соответствующей реализации удобного инициализатора суперкласса.

В приведенном ниже примере определяется базовый класс с именем Vehicle. Этот базовый класс объявляет хранимое свойство numberOfWheels, с по умолчанию Intзначения 0numberOfWheelsСвойство используется вычисленного свойство , названное descriptionсоздать Stringописание характеристик автомобиля:

class Vehicle {

var numberOfWheels = 0

var description: String {

return "\(numberOfWheels) wheel(s)"

}

}

VehicleКласс предоставляет значение по умолчанию для его только хранящегося имущества, а также не предоставляет сам пользовательских инициализаторами. В результате он автоматически получает инициализатор по умолчанию, как описано в разделе «Инициализаторы по умолчанию» . По умолчанию инициализатор (при наличии) всегда обозначаются инициализатор класса, и может быть использован для создания нового Vehicleэкземпляра с numberOfWheelsиз 0:

let vehicle = Vehicle()

print("Vehicle: \(vehicle.description)")

// Vehicle: 0 wheel(s)

Следующий пример определяет подкласс Vehicleназывается Bicycle:

class Bicycle: Vehicle {

override init() {

super.init()

numberOfWheels = 2

}

}

BicycleПодкласс определяет пользовательский назначенный инициализатору, init(). Этот назначенный инициализатор соответствует назначенному инициализатору из суперкласса Bicycle, и поэтому Bicycleверсия этого инициализатора помечается overrideмодификатором.

init()Инициализатор Bicycleначинается с вызова super.init(), который вызывает инициализатор по умолчанию для Bicycleсуперкласса класса, Vehicle. Это гарантирует, что numberOfWheelsунаследованное свойство инициализируется Vehicleдо того Bicycle, как появится возможность изменить свойство. После вызова super.init()исходное значение numberOfWheelsзаменяется новым значением 2.

Если вы создаете экземпляр класса Bicycle, вы можете вызвать его унаследованное descriptionвычисленное свойство, чтобы увидеть, как его numberOfWheelsсвойство было обновлено:

let bicycle = Bicycle()

print("Bicycle: \(bicycle.description)")

// Bicycle: 2 wheel(s)

Если инициализатор подкласса не выполняет настройку на этапе 2 процесса инициализации, и суперкласс имеет инициализатор с нулевым аргументом, вы можете пропустить вызов super.init()после присвоения значений всем сохраненным свойствам подкласса.

Этот пример определяет другой подкласс Vehicle, называемый Hoverboard. В своем инициализаторе Hoverboardкласс устанавливает только свое colorсвойство. Вместо явного вызова super.init()этого инициализатора полагается на неявный вызов инициализатора своего суперкласса для завершения процесса.

class Hoverboard: Vehicle {

var color: String

init(color: String) {

self.color = color

// super.init() implicitly called here

}

override var description: String {

return "\(super.description) in a beautiful \(color)"

}

}

Экземпляр Hoverboardиспользует количество колес по умолчанию, предоставленное Vehicleинициализатором.

let hoverboard = Hoverboard(color: "silver")

print("Hoverboard: \(hoverboard.description)")

// Hoverboard: 0 wheel(s) in a beautiful silver

ЗАМЕТКА

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

Автоматическое наследование инициализатора

Как упомянуто выше, подклассы не наследуют свои инициализаторы суперкласса по умолчанию. Однако суперкласс Инициализаторы будут автоматически наследуются при соблюдении определенных условий. На практике это означает, что вам не нужно записывать переопределения инициализатора во многих распространенных сценариях, и вы можете наследовать инициализаторы суперкласса с минимальными усилиями, когда это безопасно.

Предполагая, что вы предоставляете значения по умолчанию для любых новых свойств, которые вы вводите в подкласс, применяются следующие два правила:

Правило 1

Если ваш подкласс не определяет назначенные инициализаторы, он автоматически наследует все назначенные инициализаторы суперкласса.

Правило 2

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

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

ЗАМЕТКА

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

Назначенные и удобные инициализаторы в действии

В следующем примере показаны назначенные инициализаторы, удобные инициализаторы и автоматическое наследование инициализатора в действии. Этот пример определяет иерархию трех классов , называемых FoodRecipeIngredientи ShoppingListItem, и показывает , как их Инициализаторы взаимодействуют.

Вызывается базовый класс в иерархии Food, который представляет собой простой класс для инкапсуляции названия продукта питания. FoodКласс вводит единое Stringсвойство nameи предоставляет два инициализаторов для создания Foodэкземпляров:

class Food {

var name: String

init(name: String) {

self.name = name

}

convenience init() {

self.init(name: "[Unnamed]")

}

}

Классы не имеют элементного инициализатора по умолчанию, и поэтому Foodкласс предоставляет назначенный инициализатор, который принимает один вызываемый аргумент name. Этот инициализатор можно использовать для создания нового Foodэкземпляра с определенным именем:

let namedMeat = Food(name: "Bacon")

// namedMeat's name is "Bacon"

Инициализатор из класса предоставляются в качестве назначенного инициализатора, поскольку она гарантирует , что все сохраненные свойства нового экземпляра полностью инициализированы. Класс не имеет суперкласса, и поэтому инициализатор не нужно звонить , чтобы завершить инициализацию.init(name: String)FoodFoodFoodinit(name: String)super.init()

FoodКласс также обеспечивает удобство инициализатору, init()без каких - либо аргументов. init()Инициализатор предоставляет имя замещающего по умолчанию для новых продуктов питания путем передач через к Foodклассу с значением :init(name: String)name[Unnamed]

let mysteryMeat = Food()

// mysteryMeat's name is "[Unnamed]"

Второй класс в иерархии является подклассом Foodвызываемого RecipeIngredient. В RecipeIngredientмодели класса ингредиент в рецепте приготовления пищи. Он вводит Intсвойство с именем quantity(в дополнение к nameсвойству, от которого он наследуется Food) и определяет два инициализатора для создания RecipeIngredientэкземпляров:

class RecipeIngredient: Food {

var quantity: Int

init(name: String, quantity: Int) {

self.quantity = quantity

super.init(name: name)

}

override convenience init(name: String) {

self.init(name: name, quantity: 1)

}

}

RecipeIngredientКласс имеет один назначенный инициализатору, , который может быть использован для заполнения всех свойств нового экземпляра. Этот инициализатор начинает с присвоения переданного аргумента свойству, которое является единственным новым свойством, введенным . После этого инициализатор делегирует вплоть до инициализатора класса. Этот процесс удовлетворяет требованиям проверки безопасности 1 из описанной выше двухфазной инициализации.init(name: String, quantity: Int)RecipeIngredientquantityquantityRecipeIngredientinit(name: String)Food

 

RecipeIngredientтакже определяет удобный инициализатор, который используется для создания экземпляра только по имени. Этот удобный инициализатор предполагает количество для любого экземпляра, созданного без явного количества. Определение этого удобного инициализатора делает экземпляры более быстрыми и удобными для создания, а также позволяет избежать дублирования кода при создании нескольких экземпляров с одним количеством . Этот удобный инициализатор просто делегирует инициализатору класса, передавая значение .init(name: String)RecipeIngredient1RecipeIngredientRecipeIngredientRecipeIngredientquantity1

Удобство инициализатор обеспечивается принимает те же параметры, что и назначенный инициализаторе с . Поскольку этот удобный инициализатор переопределяет указанный инициализатор из своего суперкласса, он должен быть помечен модификатором .init(name: String)RecipeIngredientinit(name: String)Foodoverride

Несмотря на то, что он RecipeIngredientпредоставляет инициализатор в качестве удобного инициализатора, он, тем не менее, предоставил реализацию всех инициализаторов своего суперкласса. Следовательно, автоматически наследует все удобные инициализаторы своего суперкласса.init(name: String)RecipeIngredientRecipeIngredient

В этом примере суперкласс для RecipeIngredientis Food, который имеет единственный удобный инициализатор, называется init(). Поэтому этот инициализатор наследуется RecipeIngredient. Унаследованная версия init()функций точно так же, как Foodверсия, за исключением того, что она делегирует RecipeIngredientверсии, а не версии.init(name: String)Food

Все три из этих инициализаторов могут быть использованы для создания новых RecipeIngredientэкземпляров:

let oneMysteryItem = RecipeIngredient()

let oneBacon = RecipeIngredient(name: "Bacon")

let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

Третий и последний класс в иерархии является подклассом RecipeIngredientвызываемого ShoppingListItem. В ShoppingListItemмодели класса рецепт ингредиент , как он появляется в списке покупок.

Каждый элемент в списке покупок начинается как «не купленный». Чтобы представить этот факт, ShoppingListItemвводится логическое свойство с именем purchasedпо умолчанию falseShoppingListItemтакже добавляет вычисляемое descriptionсвойство, которое предоставляет текстовое описание ShoppingListItemэкземпляра:

class ShoppingListItem: RecipeIngredient {

var purchased = false

var description: String {

var output = "\(quantity) x \(name)"

output += purchased ? " ✔" : " ✘"

return output

}

}

ЗАМЕТКА

ShoppingListItemне определяет инициализатор для предоставления начального значения purchased, потому что элементы в списке покупок (как смоделировано здесь) всегда начинаются с покупки.

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

Вы можете использовать все три унаследованных инициализатора для создания нового ShoppingListItemэкземпляра:

var breakfastList = [

ShoppingListItem(),

ShoppingListItem(name: "Bacon"),

ShoppingListItem(name: "Eggs", quantity: 6),

]

breakfastList[0].name = "Orange juice"

breakfastList[0].purchased = true

for item in breakfastList {

print(item.description)

}

// 1 x Orange juice ✔

// 1 x Bacon ✘

// 6 x Eggs ✘

Здесь новый вызванный массив breakfastListсоздается из литерала массива, содержащего три новых ShoppingListItemэкземпляра. Тип массива выводится как [ShoppingListItem]. После создания массива имя ShoppingListItemв начале массива изменяется с "[Unnamed]"на и помечается как приобретенное. Печать описания каждого элемента в массиве показывает, что их состояния по умолчанию установлены так, как ожидалось."Orange juice"

Сбойные инициализаторы

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

Чтобы справиться с условиями инициализации, которые могут дать сбой, определите один или несколько сбойных инициализаторов как часть определения класса, структуры или перечисления. Вы пишете неудачный инициализатор, помещая знак вопроса после initключевого слова ( init?).

ЗАМЕТКА

Вы не можете определить неисправный и неисправный инициализатор с одинаковыми типами параметров и именами.

Сбойный инициализатор создает необязательное значение типа, который он инициализирует. Вы пишете в неисправном инициализаторе, чтобы указать точку, в которой может быть инициирован сбой инициализации.return nil

ЗАМЕТКА

Строго говоря, инициализаторы не возвращают значение. Скорее, их роль заключается в том, чтобы обеспечить selfполную и правильную инициализацию к моменту окончания инициализации. Несмотря на то, что вы пишете, чтобы вызвать ошибку инициализации, вы не используете ключевое слово, чтобы указать успешность инициализации.return nilreturn

Например, неудачные инициализаторы реализованы для числовых преобразований типов. Чтобы гарантировать, что преобразование между числовыми типами точно поддерживает значение, используйте init(exactly:)инициализатор. Если преобразование типа не может сохранить значение, инициализатор завершается ошибкой.

let wholeNumber: Double = 12345.0

let pi = 3.14159



if let valueMaintained = Int(exactly: wholeNumber) {

print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")

}

// Prints "12345.0 conversion to Int maintains value of 12345"



let valueChanged = Int(exactly: pi)

// valueChanged is of type Int?, not Int



if valueChanged == nil {

print("\(pi) conversion to Int does not maintain value")

}

// Prints "3.14159 conversion to Int does not maintain value"

В приведенном ниже примере определяется структура с именем Animalс Stringименем константы speciesAnimalСтруктура также определяет failable инициализатор с одним параметром называется species. Этот инициализатор проверяет, является ли speciesзначение, переданное инициализатору, пустой строкой. Если найдена пустая строка, возникает ошибка инициализации. В противном случае значение speciesсвойства устанавливается и инициализация завершается успешно:

struct Animal {

let species: String

init?(species: String) {

if species.isEmpty { return nil }

self.species = species

}

}

Вы можете использовать этот неисправный инициализатор, чтобы попытаться инициализировать новый Animalэкземпляр и проверить, успешно ли выполнена инициализация:

let someCreature = Animal(species: "Giraffe")

// someCreature is of type Animal?, not Animal



if let giraffe = someCreature {

print("An animal was initialized with a species of \(giraffe.species)")

}

// Prints "An animal was initialized with a species of Giraffe"

Если вы передадите пустое строковое значение параметру инициализатора species, который не может быть выполнен, инициализатор вызовет ошибку инициализации:

let anonymousCreature = Animal(species: "")

// anonymousCreature is of type Animal?, not Animal



if anonymousCreature == nil {

print("The anonymous creature could not be initialized")

}

// Prints "The anonymous creature could not be initialized"

ЗАМЕТКА

Проверка на пустое строковое значение (например, ""вместо "Giraffe") отличается от проверки на nilналичие необязательного String значения. В приведенном выше примере пустая строка ( "") является допустимой, необязательной String. Однако для животного не подходит пустая строка в качестве значения его speciesсвойства. Чтобы смоделировать это ограничение, сбойный инициализатор вызывает сбой инициализации, если найдена пустая строка.

Сбойные инициализаторы для перечислений

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

Приведенный ниже пример определяет перечисление с именем TemperatureUnit, с тремя возможными состояниями ( kelvincelsiusи fahrenheit). Отказавшийся инициализатор используется, чтобы найти подходящий случай перечисления для Characterзначения, представляющего символ температуры:

enum TemperatureUnit {

case kelvin, celsius, fahrenheit

init?(symbol: Character) {

switch symbol {

case "K":

self = .kelvin

case "C":

self = .celsius

case "F":

self = .fahrenheit

default:

return nil

}

}

}

Вы можете использовать этот неисправный инициализатор, чтобы выбрать подходящий случай перечисления для трех возможных состояний и вызвать сбой инициализации, если параметр не соответствует одному из этих состояний:

let fahrenheitUnit = TemperatureUnit(symbol: "F")

if fahrenheitUnit != nil {

print("This is a defined temperature unit, so initialization succeeded.")

}

// Prints "This is a defined temperature unit, so initialization succeeded."



let unknownUnit = TemperatureUnit(symbol: "X")

if unknownUnit == nil {

print("This is not a defined temperature unit, so initialization failed.")

}

// Prints "This is not a defined temperature unit, so initialization failed."

Сбойные инициализаторы для перечислений с необработанными значениями

Перечисления с необработанными значениями автоматически получают сбойный инициализатор, init?(rawValue:)который принимает вызываемый параметр rawValueсоответствующего типа необработанных значений и выбирает соответствующий случай перечисления, если он найден, или запускает ошибку инициализации, если не существует соответствующего значения.

Вы можете переписать TemperatureUnitпример сверху, чтобы использовать необработанные значения типа Characterи воспользоваться init?(rawValue:)инициализатором:

enum TemperatureUnit: Character {

case kelvin = "K", celsius = "C", fahrenheit = "F"

}



let fahrenheitUnit = TemperatureUnit(rawValue: "F")

if fahrenheitUnit != nil {

print("This is a defined temperature unit, so initialization succeeded.")

}

// Prints "This is a defined temperature unit, so initialization succeeded."



let unknownUnit = TemperatureUnit(rawValue: "X")

if unknownUnit == nil {

print("This is not a defined temperature unit, so initialization failed.")

}

// Prints "This is not a defined temperature unit, so initialization failed."

Распространение ошибки инициализации

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

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

ЗАМЕТКА

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

В приведенном ниже примере определяется подкласс Productвызываемого CartItem. В CartItemмодели класса элемент в интернет - корзине. CartItemпредставляет хранимое свойство константы с именем quantityи гарантирует, что это свойство всегда имеет значение по крайней мере 1:

class Product {

let name: String

init?(name: String) {

if name.isEmpty { return nil }

self.name = name

}

}



class CartItem: Product {

let quantity: Int

init?(name: String, quantity: Int) {

if quantity < 1 { return nil }

self.quantity = quantity

super.init(name: name)

}

}

Отказавший инициализатор для CartItemзапуска проверяет, что он получил quantityзначение 1или больше. Если значение quantityнедопустимо, весь процесс инициализации сразу завершается неудачно, и дальнейший код инициализации не выполняется. Аналогично, неисправный инициализатор для Productпроверяет nameзначение, и процесс инициализатора завершается неудачно, если nameэто пустая строка.

Если вы создаете CartItemэкземпляр с непустым именем и количеством 1или более, инициализация завершается успешно:

if let twoSocks = CartItem(name: "sock", quantity: 2) {

print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")

}

// Prints "Item: sock, quantity: 2"

Если вы попытаетесь создать CartItemэкземпляр со quantityзначением 0CartItemинициализатор вызовет сбой инициализации:

if let zeroShirts = CartItem(name: "shirt", quantity: 0) {

print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")

} else {

print("Unable to initialize zero shirts")

}

// Prints "Unable to initialize zero shirts"

Аналогично, если вы пытаетесь создать CartItemэкземпляр с пустым nameзначением, Productинициализатор суперкласса вызывает сбой инициализации:

if let oneUnnamed = CartItem(name: "", quantity: 1) {

print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")

} else {

print("Unable to initialize one unnamed product")

}

// Prints "Unable to initialize one unnamed product"

Переопределение сбойного инициализатора

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

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

ЗАМЕТКА

Вы можете переопределить сбойный инициализатор с помощью сбойного инициализатора, но не наоборот.

В приведенном ниже примере определяется класс с именем Document. Этот класс моделирует документ, который можно инициализировать с помощью nameсвойства, которое является непустым строковым значением или nil, но не может быть пустой строкой:

class Document {

var name: String?

// this initializer creates a document with a nil name value

init() {}

// this initializer creates a document with a nonempty name value

init?(name: String) {

if name.isEmpty { return nil }

self.name = name

}

}

В следующем примере определяется подкласс Documentвызываемого AutomaticallyNamedDocumentAutomaticallyNamedDocumentПодкласс перекрывает оба указанных инициализаторах введенных Document. Эти переопределения гарантируют, что AutomaticallyNamedDocumentэкземпляр имеет начальное nameзначение, "[Untitled]"если экземпляр инициализируется без имени или если в init(name:)инициализатор передана пустая строка :

class AutomaticallyNamedDocument: Document {

override init() {

super.init()

self.name = "[Untitled]"

}

override init(name: String) {

super.init()

if name.isEmpty {

self.name = "[Untitled]"

} else {

self.name = name

}

}

}

AutomaticallyNamedDocumentПереопределяет failable своего суперкласса init?(name:)инициализатору с nonfailable init(name:)инициализаторе. Поскольку дело AutomaticallyNamedDocumentс регистром пустой строки выполняется не так, как его суперкласс, его инициализатору не нужно отказывать, и поэтому вместо этого он предоставляет версию для инициализатора, не имеющую ошибки.

Вы можете использовать принудительную распаковку в инициализаторе, чтобы вызвать сбойный инициализатор из суперкласса как часть реализации ненадежного инициализатора подкласса. Например, приведенный UntitledDocumentниже подкласс всегда имеет имя "[Untitled]"и использует init(name:)инициализируемый инициализатор из своего суперкласса во время инициализации.

class UntitledDocument: Document {

override init() {

super.init(name: "[Untitled]")!

}

}

В этом случае, если init(name:)инициализатор суперкласса когда-либо вызывался с пустой строкой в ​​качестве имени, операция принудительного развертывания приведет к ошибке времени выполнения. Тем не менее, поскольку он вызывается со строковой константой, вы можете видеть, что инициализатор не выйдет из строя, поэтому в этом случае не может возникнуть ошибка времени выполнения.

Посвящение! Сбой инициализатора

Обычно вы определяете неисправный инициализатор, который создает необязательный экземпляр соответствующего типа, помещая знак вопроса после initключевого слова ( init?). Кроме того, вы можете определить сбойный инициализатор, который создает неявно развернутый необязательный экземпляр соответствующего типа. Сделайте это, поместив восклицательный знак после initключевого слова ( init!) вместо знака вопроса.

Вы можете делегировать от и init?до init!, и вы можете переопределить init?с init!и наоборот. Вы также можете делегировать из initв init!, хотя это вызовет утверждение, если init!инициализатор вызовет сбой инициализации.

Требуемые инициализаторы

Напишите requiredмодификатор перед определением инициализатора класса, чтобы указать, что каждый подкласс класса должен реализовывать этот инициализатор:

class SomeClass {

required init() {

// initializer implementation goes here

}

}

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

class SomeSubclass: SomeClass {

required init() {

// subclass implementation of the required initializer goes here

}

}

ЗАМЕТКА

Вам не нужно предоставлять явную реализацию необходимого инициализатора, если вы можете удовлетворить требование с помощью унаследованного инициализатора.

Установка значения свойства по умолчанию с помощью замыкания или функции

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

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

Вот скелетная схема того, как замыкание может использоваться для предоставления значения свойства по умолчанию:

class SomeClass {

let someProperty: SomeType = {

// create a default value for someProperty inside this closure

// someValue must be of the same type as SomeType

return someValue

}()

}

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

ЗАМЕТКА

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

Пример ниже определяет структуру Chessboard, которая называется , которая моделирует доску для игры в шахматы. В шахматы играют на доске 8х8, чередуя черные и белые квадраты.

Для представления этой игровой доски Chessboardструктура имеет единственное свойство, называемое boardColorsмассивом из 64 Boolзначений. Значение trueв массиве представляет черный квадрат, а значение falseпредставляет белый квадрат. Первый элемент в массиве представляет верхний левый квадрат на доске, а последний элемент в массиве представляет нижний правый квадрат на доске.

boardColorsМассив инициализируется с крышкой , чтобы установить свои значения цвета:

struct Chessboard {

let boardColors: [Bool] = {

var temporaryBoard = [Bool]()

var isBlack = false

for i in 1...8 {

for j in 1...8 {

temporaryBoard.append(isBlack)

isBlack = !isBlack

}

isBlack = !isBlack

}

return temporaryBoard

}()

func squareIsBlackAt(row: Int, column: Int) -> Bool {

return boardColors[(row * 8) + column]

}

}

Всякий раз, когда создается новый Chessboardэкземпляр, выполняется замыкание и boardColorsвычисляется и возвращается значение по умолчанию . Замыкание в приведенном выше примере вычисляет и устанавливает соответствующий цвет для каждого квадрата на доске во временном массиве temporaryBoard, который вызывается, и возвращает этот временный массив в качестве возвращаемого значения замыкания, как только его настройка завершена. Возвращенное значение массива хранится в boardColorsи может быть запрошено с помощью squareIsBlackAt(row:column:)служебной функции:

let board = Chessboard()

print(board.squareIsBlackAt(row: 0, column: 1))

// Prints "true"

print(board.squareIsBlackAt(row: 7, column: 7))

// Prints "false"