Перейти к содержимому
Теория · Структуры · урок 5 из 9

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

Бланк Temperature хранит температуру в градусах Цельсия. Но ваш термометр показывает градусы Фаренгейта, и пересчитывать их в уме перед каждым заполнением неудобно. Удобнее отдать бланк сотруднику: вы называете градусы Фаренгейта, а он сам пересчитывает их и вписывает в графу.

Такого сотрудника в структуре называют инициализатором:

SWIFT
struct Temperature {
    var celsius: Double

    init(fahrenheit: Double) {
        celsius = (fahrenheit - 32) * 5 / 9
    }
}

let boiling = Temperature(fahrenheit: 212)
print(boiling.celsius) // 100.0

Давайте разберём программу по шагам:

  1. Внутри Temperature объявлен init(fahrenheit:). Перед скобками с параметрами нет ни func, ни имени, а после них нет ->.
  2. Temperature(fahrenheit: 212) вызывает этот init: параметр fahrenheit получает 212.
  3. Тело пересчитывает градусы и кладёт результат в свойство celsius.
  4. print(boiling.celsius) печатает 100.0: вода кипит при 212 градусах Фаренгейта.

Свой инициализатор

Инициализатор — это код, который заполняет свойства нового экземпляра. Его объявляют словом init, а вызывают по имени типа: Temperature(fahrenheit: 212). Параметры, метки аргументов и значения по умолчанию у него такие же, как у функции. mutating инициализатору не нужен: заполнять свойства нового экземпляра — его работа.

Правило у инициализатора одно: к его концу у каждого хранимого свойства должно быть значение, своё или по умолчанию. Если какое-то свойство останется пустым, компилятор ответит return from initializer without initializing all stored properties.

Здесь картинка с сотрудником неточна. Сотрудник может по рассеянности отдать бланк с пустой графой, а Swift такой init даже не скомпилирует.

Наблюдатели willSet и didSet внутри init не срабатывают: экземпляр ещё только создаётся.

Почленный инициализатор пропадает

Способ создать экземпляр с метками всех свойств, вроде Point(x: 1, y: 2), Swift даёт структуре сам. Его называют почленным инициализатором. Но как только в структуре появляется свой init, почленный пропадает:

SWIFT
let room = Temperature(celsius: 20) // ✗
TEXT
error: incorrect argument label in call (have 'celsius:', expected 'fahrenheit:')

Компилятор видит единственный инициализатор, init(fahrenheit:), и подсказывает, что метка должна быть fahrenheit:. Если нужны оба способа, почленный дописывают сами, ещё одним init внутри структуры: init(celsius: Double) { self.celsius = celsius }. Теперь у Temperature два инициализатора, и Swift выбирает нужный по меткам.

Частая ошибка

Параметр инициализатора назвали так же, как свойство, и забыли self:

SWIFT
struct Box {
    var width: Int

    init(width: Int) {
        width = width // ✗
    }
}
TEXT
error: cannot assign to value: 'width' is a 'let' constant

Программа не компилируется. Внутри init имя width означает параметр, а параметр — константа. Запись width = width пытается положить параметр в него же. Чтобы обратиться к свойству, напишите self.width = width: слева свойство экземпляра, справа параметр.

Главное

  • init заполняет свойства нового экземпляра; к его концу у каждого хранимого свойства должно быть значение.
  • Свой init убирает почленный инициализатор; если почленный нужен, его дописывают ещё одним init.
  • Когда параметр и свойство называются одинаково, свойство пишут через self: self.width = width.