Инициализация - это процесс подготовки экземпляра класса, структуры или перечисления к использованию. Этот процесс включает установку начального значения для каждого сохраненного свойства в этом экземпляре и выполнение любых других настроек или инициализации, которые требуются до того, как новый экземпляр будет готов к использованию.
Вы реализуете этот процесс инициализации , определяя инициализаторы , которые похожи на специальные методы, которые могут быть вызваны для создания нового экземпляра определенного типа. В отличие от инициализаторов 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
, с тремя постоянными свойствами называемых red
, green
и 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
структура для представления геометрического прямоугольника. В примере требуются две вспомогательные структуры с именем Size
and 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:)
функционально такой же, как и элементный инициализатор, который структура получила бы, если бы у нее не было своих собственных пользовательских инициализаторов. Этот инициализатор просто присваивает значения аргумента origin
and 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
значения 0
. numberOfWheels
Свойство используется вычисленного свойство , названное 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.
Назначенные и удобные инициализаторы в действии
В следующем примере показаны назначенные инициализаторы, удобные инициализаторы и автоматическое наследование инициализатора в действии. Этот пример определяет иерархию трех классов , называемых Food
, RecipeIngredient
и 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)
Food
Food
Food
init(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)
RecipeIngredient
quantity
quantity
RecipeIngredient
init(name: String)
Food
RecipeIngredient
также определяет удобный инициализатор, который используется для создания экземпляра только по имени. Этот удобный инициализатор предполагает количество для любого экземпляра, созданного без явного количества. Определение этого удобного инициализатора делает экземпляры более быстрыми и удобными для создания, а также позволяет избежать дублирования кода при создании нескольких экземпляров с одним количеством . Этот удобный инициализатор просто делегирует инициализатору класса, передавая значение .init(name: String)
RecipeIngredient
1
RecipeIngredient
RecipeIngredient
RecipeIngredient
quantity
1
Удобство инициализатор обеспечивается принимает те же параметры, что и назначенный инициализаторе с . Поскольку этот удобный инициализатор переопределяет указанный инициализатор из своего суперкласса, он должен быть помечен модификатором .init(name: String)
RecipeIngredient
init(name: String)
Food
override
Несмотря на то, что он RecipeIngredient
предоставляет инициализатор в качестве удобного инициализатора, он, тем не менее, предоставил реализацию всех инициализаторов своего суперкласса. Следовательно, автоматически наследует все удобные инициализаторы своего суперкласса.init(name: String)
RecipeIngredient
RecipeIngredient
В этом примере суперкласс для RecipeIngredient
is 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
по умолчанию false
. ShoppingListItem
также добавляет вычисляемое 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 nil
return
Например, неудачные инициализаторы реализованы для числовых преобразований типов. Чтобы гарантировать, что преобразование между числовыми типами точно поддерживает значение, используйте 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
именем константы species
. Animal
Структура также определяет 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
, с тремя возможными состояниями ( kelvin
, celsius
и 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
значением 0
, CartItem
инициализатор вызовет сбой инициализации:
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
вызываемого AutomaticallyNamedDocument
. AutomaticallyNamedDocument
Подкласс перекрывает оба указанных инициализаторах введенных 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"
0 комментариев