Основы Swift / 13.1. Инициализация - Основы


Видео


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

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

Объекты классов также могут реализовывать деинициализаторы, который выполняет все произвольные действия по очистке прежде, чем объект класса будет деаллоцирован из памяти.
Конструктор и инициализатор
В Swift используется понятие инициализатора, что равнозначно понятию конструктора в других языках. Другой термин, вероятнее всего, дан ввиду наличия различных особенностей и ограничений на инициализатор - если в C++/C конструктор может создать объект, не все члены которого инициализированы, то в Swift такой подход недопустим по соображениям безопасности. В настоящем курсе конструктор и инициализатор принимаются как синонимы, ровно как и деструктор и деинициализатор.
Установка изначальных значений для хранимых свойств
Классы и структуры должны установить все свои хранимые свойства в подходящие изначальные значения во время того, как класс или структура создаются. Хранимые свойства не могут быть оставлены в промежуточном состоянии.

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

Когда Вы присваиваете значение по-умолчанию хранимому свойству или задаёте его в инициализаторе, то оно устанавливается напрямую, минуя вызов любых наблюдателей свойства.
Инициализаторы
Инициализаторы вызываются ля создания нового экземпляра определённого типа. В своей простейшей форме инициализатор похож на метод объекта без параметров, который записывается через ключевое слово init:
@{13.1\1}
init() {
    // выполнить здесь некую инициализацию
}
@{13.1\2}
Пример ниже определяет новую структуру с названием Fahrenheit для хранения температуры, выраженной в шкале Фаренгейта. Структура Fahrenheit имеет одно хранимое свойство, temperature, типа Double:
struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// Выводит "The default temperature is 32.0° Fahrenheit"
Структура определяет единственный конструктор, init, без параметров, который инициализирует хранимое значение температуры значением 32.0 (точка замерзания воды в градусах Фаренгейта).
Значения свойств по-умолчанию
Вы можете задать изначальное значение для хранимого свойства из его конструктора, как это показано выше. С другой стороны, Вы можете задать значение свойству по-умолчанию как часть объявления этого свойства. Вы можете определить значение свойства по-умолчанию, присвоив изначальное значение свойству, когда оно определяется.

Если свойство всегда принимает одно и то же изначальное значение, то предоставьте ему значение по-умолчанию вместо задания в конструкторе. Результат конечный будет одинаков, но значение по-умолчанию делает инициализацию свойства более близкой к его объявлению. Это делается для более кратких, понятных конструкторов и позволяет Вам выводить тип свойства из его значения по-умолчанию. Значение по-умолчанию также делает более простым для Вас использование конструкторов по-умолчанию и наследование конструкторов, как это будет описано подробнее далее в этой главе.
@{13.1\3}
Вы можете записать структуру Fahrenheit из примера выше в более простой форме, предоставив значение по-умолчанию для её свойства temperature в точке его объявления:
struct Fahrenheit {
    var temperature = 32.0
}
Произвольная инициализация
Вы можете изменить процесс инициализации входными параметрами и опциональными типами свойств, или присвоением константных свойств в процессе инициализации, как будет описано в следующих разделах.
Параметры инициализации
Вы можете предоставить параметры инициализации в качестве части определения инициализатора для определения типов и имён значений, которые кастомизируют процесс инициализации. Параметры инициализации имеют те же возможности и синтаксис, что и параметры методов и функций.
@{13.1\4}
Следующий пример определяет структуру 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 теперь равен 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius теперь равен 0.0
Первый конструктор имеет один параметр инициализации с ярлыком аргумента fromFahrenheit и именем параметра fahrenheit. Второй инициализатор имеет единственный параметр с ярлыком аргумента fromKelvin и именем параметра kelvin. Оба инициализатора конвертируют их единственный аргумент в корреспондирующее значение в Цельсиях и сохраняют это значение в свойстве temperatureInCelsius.
Имена параметров и ярлыки аргументов
Как и в случае с функциональными параметрами и параметрами методов параметры инициализации могут иметь как имя параметра для использования внутри тела иницилазиатора, так и ярлык аргумента для использования при вызове конструктора.

Однако инициализаторы не имеют идентифицирующего имении функции перед своими круглыми скобками в отличие от того, как это делают функции и иметоды. Ввиду этого имена и типы параметров инициализаторов играют значимую роль в идентификации того, какой конструктор должен быть вызван. В результате этого Swift предоставляет автоматические ярлыки аргументов для каждого параметра в конструкторе, если этого не сделаете Вы сами.
@{13.1\5\1}
Следующий пример определяет структуру с названием 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
    }
}
@{13.1\5\2}
Оба инициализатора могут быть использованы для создания нового объекта типа Color путём предоставления значений для каждого параметра:
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
@{13.1\5\3}
Заметьте, что невозможно вызвать эти инициализаторы без использования ярлыков аргументов. Ярлыки аргументов всегда должны быть использовать в конструкторе, если они определены, а опускание их вызовет ошибку времени компиляции:
let veryGreen = Color(0.0, 1.0, 0.0)
// это вызовет ошибку времени компиляции - ярлыки аргументов необходимы
Параметры инициализации без ярлыков аргументов
Если Вы не хотите использовать ярлык аргумента для параметра инициализации, напишите знак нижнего подчёркивания (_) вместо явного ярлыка аргумента для этого параметра, чтобы перезаписать поведение по-умолчанию.
@{13.1\6}
Здесь расширенная версия раннего примера Celsius с дополнительным конструктором для создания нового объекта типа Celcius из значения типа 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 теперь равен 37.0
Вызов конструктора Celsius(37.0) определённо не требует указания ярлыка аргумента. Так что этот инициализатор в виде init(_ celsius: Double), так чтобы его можно было вызвать путём предоставления безымянного значения типа Double.
Свойства-опционалы
Если Ваш собственный тип иметь хранимое свойство, которое логически может иметь "никакое значение", так как оно возможно может не быть возможно установлено в ходе инициализации, или так как ему разрешено не иметь значения в какой-то момент будущего, то будет верным задать его в виде опционального типа. Свойства опционального типа автоматически инициализируются значением nil, что означает, что свойство имеет "значения пока нет" в процессе инициализации.
@{13.1\7}
Следующий пример определяет класс с названием 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()
// Выведет "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."
Ответ на вопрос опроса не может быть известен прежде, чем будет задан вопрос, так что свойство response объявлено с типом String? или "опциональная String". Ему автоматически присваивается значение по-умолчанию nil, означающее "здесь ещё нет строки", когда объект типа SurveyQuestion инициализируется.
Присваивание константных свойств в процессе инициализации
Вы можете присвоить значение константному свойству в любой момент в процессе инициализации, хотя в конце процесса инициализации ему должно быть задано окончательное значение. Как только константному свойству задано значение, в дальнейшем оно не может быть изменено.

Для объектов классов константное свойство может быть изменено в процессе инициализации только классом, который его вводит. Оно не может быть изменено подклассом.
@{13.1\8}
Вы можете вновь рассмотреть пример 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()
// Выведет "How about beets?"
beetsQuestion.response = "I also like beets. But not with cheese."
Конструкторы по-умолчанию
Swift предоставляет дефолтные конструкторы для любой структуры или класса, которые предоставляют значения по-умолчанию для всех своих свойств и не предоставляют ни одного собственного конструктора. Дефолтный конструктор просто создаёт новый объект, все свойства которого заданы в их изначальные значения.
@{13.1\9}
Этот пример определяет класс с названием ShoppingListItem, который инкапсулирует имя, количество и статус покупки вещи в листе товаров:
class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()
Так как все свойства класса ShoppingListItem имеют значения по-умолчанию, а также так как это класс является базовым, то ShoppingListItem автоматически получает реализацию конструктора по-умолчанию, которая создаёт новый объект со всеми его свойствами, заданными в значения по-умолчанию. (Свойство name имеет тип String?, так что оно автоматически получает дефолтное значение nil, даже если никакого значения не будет записано в коде.) Пример выше использует дефолтный конструктор для класса ShoppingListItem для создания нового объекта для класса с синтаксисом инициализатора, записываемого как ShoppingListItem() и присваивает его новой переменной item.
Почленные инициализаторы для типов-структур
Структурные типы получают автоматически почленный конструктор, если они не определяют своих собственных конструкторов. В отличие от конструктора по-умолчанию структура получает почленный конструктор, даже если её хранимые свойства не имеют значений по-умолчанию.

Полученный конструктор - это краткий путь для инициализации свойств-членов новых объектов-структур. Начальные значения для свойств нового объекта могут быть переданы в почленный конструктор по имени.
@{13.1\10}
Пример ниже определяет структуру с названием 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)