Spec-Zone.ru › Swift Language

Инициализация

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

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

Вы реализуете этот процесс инициализации, определив инициализаторы, которые представляют собой специальные методы, которые можно вызывать для создания нового экземпляра определенного типа. В отличие от инициализаторов в 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 из <doc:Initialization#Initialization-Parameters> выше, с дополнительным инициализатором для создания нового экземпляра 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"

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

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

Правила работы делегирования инициализаторов и допустимые формы делегирования различаются для типов значений и типов классов. Типы значений (структуры и перечисления) не поддерживают наследование, и поэтому их процесс делегирования инициализаторов относительно прост, поскольку они могут делегировать только другому инициализатору, который они предоставляют сами. Однако классы могут наследоваться от других классов, как описано в Наследовании. Это означает, что классы несут дополнительную ответственность за обеспечение того, чтобы все хранимые свойства, которые они наследуют, получали соответствующее значение во время инициализации. Эти обязанности описаны в <doc:Initialization#Class-Inheritance-and-Initialization> ниже.

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

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

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

В следующем примере определяется пользовательская структура Rect для представления геометрического прямоугольника. Пример требует двух вспомогательных структур под названием Size и 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 и 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 определяет два типа инициализаторов для типов классов, чтобы гарантировать, что все хранимые свойства получают начальное значение. Они известны как назначенные инициализаторы и вспомогательные инициализаторы.

Назначенные инициализаторы и вспомогательные инициализаторы

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

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

Каждый класс должен иметь как минимум один назначенный инициализатор. В некоторых случаях это требование выполняется путем наследования одного или нескольких назначенных инициализаторов от суперкласса, как описано в <doc:Initialization#Automatic-Initializer-Inheritance> ниже.

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

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

Синтаксис назначенных и вспомогательных инициализаторов

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

init(<#parameters#>) {
   <#statements#>
}

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

convenience init(<#parameters#>) {
   <#statements#>
}

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

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

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

  • Правило 2: Вспомогательный инициализатор должен вызывать другой инициализатор из того же класса.

  • Правило 3: Вспомогательный инициализатор должен в конечном итоге вызвать назначенный инициализатор.

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

  • Назначенные инициализаторы всегда делегируют вверх.
  • Вспомогательные инициализаторы всегда делегируют по горизонтали.

Эти правила иллюстрируются на рисунке ниже:

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

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

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

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

Инициализация в две фазы

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

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

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

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

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

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

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

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

  • Проверка безопасности 4: инициализатор не может вызывать методы экземпляра, считывать значения свойств экземпляра или ссылаться на self в качестве значения до завершения первой фазы инициализации.

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

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

Фаза 1

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

Фаза 2

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

Вот как выглядит фаза 1 для вызова инициализации для гипотетического подкласса и суперкласса:

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

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

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

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

Вот как выглядит фаза 2 для того же вызова инициализации:

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

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

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

Наследование и переопределение инициализаторов

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

Примечание: инициализаторы суперклассов наследуются в определенных обстоятельствах, но только тогда, когда это безопасно и уместно. Дополнительную информацию см. ниже в <doc:Initialization#Automatic-Initializer-Inheritance>.

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

Когда вы пишете инициализатор подкласса, соответствующий назначенному инициализатору суперкласса, вы фактически предоставляете переопределение этого назначенного инициализатора. Поэтому вы должны написать модификатор override перед определением инициализатора подкласса. Это верно даже если вы переопределяете автоматически предоставленный инициализатор по умолчанию, как описано в <doc:Initialization#Default-Initializers>.

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

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

И наоборот, если вы пишете инициализатор подкласса, соответствующий вспомогательному инициализатору суперкласса, этот вспомогательный инициализатор суперкласса никогда не может быть вызван напрямую вашим подклассом, в соответствии с правилами, описанными выше в <doc:Initialization#Initializer-Delegation-for-Class-Types>. Следовательно, ваш подкласс не (строго говоря) предоставляет переопределение инициализатора суперкласса. В результате вы не пишете модификатор override при предоставлении соответствующей реализации вспомогательного инициализатора суперкласса.

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

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

Класс Vehicle предоставляет значение по умолчанию для единственного хранимого свойства и не предоставляет собственных пользовательских инициализаторов. В результате он автоматически получает инициализатор по умолчанию, как описано в <doc:Initialization#Default-Initializers>. Инициализатор по умолчанию (при его наличии) всегда является назначенным инициализатором для класса и может использоваться для создания нового экземпляра 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, поэтому версия этого инициализатора помечена модификатором 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)

Если инициализатор подкласса не выполняет никаких изменений во второй фазе процесса инициализации и суперкласс имеет синхронный назначенный инициализатор без аргументов, вы можете опустить вызов super.init() после присвоения значений всем хранимым свойствам подкласса. Если инициализатор суперкласса асинхронный, вам необходимо явно написать await 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:

Классы не имеют инициализатора по умолчанию, и поэтому класс 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:

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

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

Вспомогательный инициализатор init(name: String), предоставленный классом RecipeIngredient, принимает те же параметры, что и назначенный инициализатор init(name: String) из класса Food. Поскольку этот вспомогательный инициализатор переопределяет назначенный инициализатор из своего суперкласса, он должен быть помечен модификатором override (как описано в <doc:Initialization#Initializer-Inheritance-and-Overriding>).

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

В этом примере суперклассом для RecipeIngredient является 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 doesn't maintain value")
}
// Prints "3.14159 conversion to Int doesn't maintain value"

В приведенном ниже примере определена структура, называемая Animal, со свойством константы String, называемым species. Структура Animal также определяет инициализатор, допускающий ошибку, с одним параметром, называемым 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 couldn't be initialized")
}
// Prints "The anonymous creature couldn't 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 isn't a defined temperature unit, so initialization failed.")
}
// Prints "This isn't 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 isn't a defined temperature unit, so initialization failed.")
}
// Prints "This isn't 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"

Переопределение инициализатора с возможностью ошибки

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

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

Примечание: Вы можете переопределить инициализатор с возможностью ошибки с помощью инициализатора без возможности ошибки, но не наоборот.

В примере ниже определён класс, называемый 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 переопределяет инициализатор суперкласса с возможностью ошибки init?(name:) с помощью инициализатора без возможности ошибки 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!, хотя это вызовет утверждение, если инициализатор 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
    }()
}

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

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

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

Для представления этой шахматной доски структура 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"

This source file is part of the Swift.org open source project
Copyright © 2014 - 2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API