Swift – инициализация
Классы, структуры и перечисления, однажды объявленные в Swift 4, инициализируются для подготовки экземпляра класса. Начальное значение инициализируется для сохраненного свойства, а также для новых экземпляров. Инициализируются значения, чтобы продолжить работу. Ключевое слово для создания функции инициализации выполняется методом init (). Инициализатор Swift 4 отличается от Objective-C тем, что он не возвращает никаких значений. Его функция заключается в проверке инициализации вновь созданных экземпляров перед их обработкой. Swift 4 также предоставляет процесс «деинициализации» для выполнения операций управления памятью после освобождения экземпляров.
Роль инициализатора для сохраненных свойств
Сохраненное свойство должно инициализировать экземпляры для своих классов и структур перед обработкой экземпляров. Хранимые свойства используют инициализатор для назначения и инициализации значений, тем самым устраняя необходимость вызова наблюдателей свойств. Инициализатор используется в хранимом свойстве
- Создать начальное значение.
- Чтобы назначить значение свойства по умолчанию в определении свойства.
- Для инициализации экземпляра для определенного типа данных используется init (). Внутри функции init () аргументы не передаются.
Создать начальное значение.
Чтобы назначить значение свойства по умолчанию в определении свойства.
Для инициализации экземпляра для определенного типа данных используется init (). Внутри функции init () аргументы не передаются.
Синтаксис
init() < //New Instance initialization goes here >
пример
struct rectangle var length: Double var breadth: Double init() length = 6 breadth = 12 > > var area = rectangle() print("area of rectangle is \(area.length*area.breadth)")
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
area of rectangle is 72.0
Здесь структура ‘rectangle’ инициализируется с длиной и шириной элементов как ‘Double’ типы данных. Метод Init () используется для инициализации значений для вновь создаваемых элементов длины и двойника. Площадь прямоугольника вычисляется и возвращается путем вызова функции прямоугольника.
Установка значений свойств по умолчанию
Язык Swift 4 предоставляет функцию Init () для инициализации значений сохраненных свойств. Кроме того, пользователь может инициализировать значения свойств по умолчанию при объявлении членов класса или структуры. Когда свойство принимает одно и то же значение во всей программе, мы можем объявить его только в разделе объявления, а не инициализировать его в init (). Установка значений свойств по умолчанию позволяет пользователю, когда наследование определено для классов или структур.
struct rectangle var length = 6 var breadth = 12 > var area = rectangle() print("area of rectangle is \(area.length*area.breadth)")
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
area of rectangle is 72
Здесь вместо объявления длины и ширины в init () значения инициализируются в самом объявлении.
Инициализация параметров
В языке Swift 4 пользователь имеет возможность инициализировать параметры как часть определения инициализатора с помощью init ().
struct Rectangle var length: Double var breadth: Double var area: Double init(fromLength length: Double, fromBreadth breadth: Double) self.length = length self.breadth = breadth area = length * breadth > init(fromLeng leng: Double, fromBread bread: Double) self.length = leng self.breadth = bread area = leng * bread > > let ar = Rectangle(fromLength: 6, fromBreadth: 12) print("area is: \(ar.area)") let are = Rectangle(fromLeng: 36, fromBread: 12) print("area is: \(are.area)")
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
area is: 72.0 area is: 432.0
Локальные и внешние параметры
Параметры инициализации имеют как локальные, так и глобальные имена параметров, аналогичные параметрам функций и методов. Объявление локального параметра используется для доступа в теле инициализации, а объявление внешнего параметра используется для вызова инициализатора. Инициализаторы Swift 4 отличаются от инициализатора функций и методов тем, что не определяют, какой инициализатор используется для вызова каких функций.
Чтобы преодолеть это, Swift 4 вводит автоматическое внешнее имя для каждого параметра в init (). Это автоматическое внешнее имя эквивалентно локальному имени, записанному перед каждым параметром инициализации.
struct Days let sunday, monday, tuesday: Int init(sunday: Int, monday: Int, tuesday: Int) self.sunday = sunday self.monday = monday self.tuesday = tuesday > init(daysofaweek: Int) sunday = daysofaweek monday = daysofaweek tuesday = daysofaweek > > let week = Days(sunday: 1, monday: 2, tuesday: 3) print("Days of a Week is: \(week.sunday)") print("Days of a Week is: \(week.monday)") print("Days of a Week is: \(week.tuesday)") let weekdays = Days(daysofaweek: 4) print("Days of a Week is: \(weekdays.sunday)") print("Days of a Week is: \(weekdays.monday)") print("Days of a Week is: \(weekdays.tuesday)")
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
Days of a Week is: 1 Days of a Week is: 2 Days of a Week is: 3 Days of a Week is: 4 Days of a Week is: 4 Days of a Week is: 4
Параметры без внешних имен
Когда внешнее имя не требуется для инициализации, подчеркивание ‘_’ используется для переопределения поведения по умолчанию.
struct Rectangle var length: Double init(frombreadth breadth: Double) length = breadth * 10 > init(frombre bre: Double) length = bre * 30 > init(_ area: Double) length = area > > let rectarea = Rectangle(180.0) print("area is: \(rectarea.length)") let rearea = Rectangle(370.0) print("area is: \(rearea.length)") let recarea = Rectangle(110.0) print("area is: \(recarea.length)")
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
area is: 180.0 area is: 370.0 area is: 110.0
Необязательные типы недвижимости
Когда хранимое свойство в каком-то экземпляре не возвращает никакого значения, это свойство объявляется с необязательным типом, указывающим, что для этого конкретного типа не возвращается «никакого значения». Когда сохраненное свойство объявляется как «необязательное», оно автоматически инициализирует значение «nil» во время самой инициализации.
struct Rectangle var length: Double? init(frombreadth breadth: Double) length = breadth * 10 > init(frombre bre: Double) length = bre * 30 > init(_ area: Double) length = area > > let rectarea = Rectangle(180.0) print("area is: \(rectarea.length)") let rearea = Rectangle(370.0) print("area is: \(rearea.length)") let recarea = Rectangle(110.0) print("area is: \(recarea.length)")
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
area is: Optional(180.0) area is: Optional(370.0) area is: Optional(110.0)
Изменение свойств константы во время инициализации
Инициализация также позволяет пользователю изменять значение постоянного свойства. Во время инициализации свойство класса позволяет его экземплярам класса изменяться суперклассом, а не подклассом. Рассмотрим, к примеру, в предыдущей программе «длина» объявлена как «переменная» в основном классе. Приведенная ниже программная переменная «length» модифицируется как «постоянная» переменная.
struct Rectangle let length: Double? init(frombreadth breadth: Double) length = breadth * 10 > init(frombre bre: Double) length = bre * 30 > init(_ area: Double) length = area > > let rectarea = Rectangle(180.0) print("area is: \(rectarea.length)") let rearea = Rectangle(370.0) print("area is: \(rearea.length)") let recarea = Rectangle(110.0) print("area is: \(recarea.length)")
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
area is: Optional(180.0) area is: Optional(370.0) area is: Optional(110.0)
Инициализаторы по умолчанию
Инициализаторы по умолчанию предоставляют новый экземпляр всем его объявленным свойствам базового класса или структуры со значениями по умолчанию.
class defaultexample var studname: String? var stmark = 98 var pass = true > var result = defaultexample() print("result is: \(result.studname)") print("result is: \(result.stmark)") print("result is: \(result.pass)")
Когда мы запускаем вышеуказанную программу, используя игровую площадку, мы получаем следующий результат. –
result is: nil result is: 98 result is: true
Вышеуказанная программа определена с именем класса как «defaultexample». Три функции-члена по умолчанию инициализируются как ‘studname?’ хранить значения ‘nil’, ‘stmark’ как 98 и ‘pass’ как логическое значение ‘true’. Аналогично значения членов в классе могут быть инициализированы как значения по умолчанию перед обработкой типов членов класса.
Поэлементные инициализаторы для типов структуры
Когда пользовательские инициализаторы не предоставлены пользователем, типы структуры в Swift 4 автоматически получат «членский инициализатор». Его основная функция заключается в инициализации новых экземпляров структуры по умолчанию для каждого элемента, а затем свойства нового экземпляра передаются элементному элементу инициализации по имени.
struct Rectangle var length = 100.0, breadth = 200.0 > let area = Rectangle(length: 24.0, breadth: 32.0) print("Area of rectangle is: \(area.length)") print("Area of rectangle is: \(area.breadth)")
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
Area of rectangle is: 24.0 Area of rectangle is: 32.0
Структуры инициализируются по умолчанию для их функций членства во время инициализации для «length» как «100.0» и «width» как «200.0». Но значения переопределяются при обработке переменных длины и ширины как 24.0 и 32.0.
Делегирование инициализатора для типов значений
Делегирование инициализатора определяется как вызов инициализаторов из других инициализаторов. Его основная функция заключается в возможности повторного использования, чтобы избежать дублирования кода между несколькими инициализаторами.
struct Stmark var mark1 = 0.0, mark2 = 0.0 > struct stdb var m1 = 0.0, m2 = 0.0 > struct block var average = stdb() var result = Stmark() init() <> init(average: stdb, result: Stmark) self.average = average self.result = result > init(avg: stdb, result: Stmark) let tot = avg.m1 - (result.mark1 / 2) let tot1 = avg.m2 - (result.mark2 / 2) self.init(average: stdb(m1: tot, m2: tot1), result: result) > > let set1 = block() print("student result is: \(set1.average.m1, set1.average.m2) \(set1.result.mark1, set1.result.mark2)") let set2 = block(average: stdb(m1: 2.0, m2: 2.0), result: Stmark(mark1: 5.0, mark2: 5.0)) print("student result is: \(set2.average.m1, set2.average.m2) \(set2.result.mark1, set2.result.mark2)") let set3 = block(avg: stdb(m1: 4.0, m2: 4.0), result: Stmark(mark1: 3.0, mark2: 3.0)) print("student result is: \(set3.average.m1, set3.average.m2) \(set3.result.mark1, set3.result.mark2)")
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
(0.0,0.0) (0.0,0.0) (2.0,2.0) 5.0,5.0) (2.5,2.5) (3.0,3.0)
Правила для делегирования инициализатора
Типы значений | Типы классов |
---|---|
Наследование не поддерживается для типов значений, таких как структуры и перечисления. Ссылка на другие инициализаторы осуществляется через self.init | Наследование поддерживается. Проверяет, что все сохраненные значения свойств инициализированы |
Наследование и инициализация класса
Типы классов имеют два вида инициализаторов, чтобы проверить, получают ли определенные сохраненные свойства начальное значение, а именно назначенные инициализаторы и удобные инициализаторы.
Назначенные инициализаторы и удобные инициализаторы
Назначенный инициализатор | Удобный инициализатор |
---|---|
Считается первичной инициализацией для класса | Рассматривается как поддерживающая инициализация для класса |
Все свойства класса инициализируются, и для дальнейшей инициализации вызывается соответствующий инициализатор суперкласса. | Назначенный инициализатор вызывается с удобным инициализатором для создания экземпляра класса для конкретного варианта использования или типа входного значения |
По крайней мере один назначенный инициализатор определен для каждого класса | Нет необходимости в обязательном определении удобных инициализаторов, когда класс не требует инициализаторов. |
Init (параметры) | удобство init (параметры) |
Программа для назначенных инициализаторов
class mainClass var no1 : Int // local storage init(no1 : Int) self.no1 = no1 // initialization > > class subClass : mainClass var no2 : Int // new subclass storage init(no1 : Int, no2 : Int) self.no2 = no2 // initialization super.init(no1:no1) // redirect to superclass > > let res = mainClass(no1: 10) let print = subClass(no1: 10, no2: 20) print("res is: \(res.no1)") print("res is: \(print.no1)") print("res is: \(print.no2)")
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
res is: 10 res is: 10 res is: 20
Программа для удобных инициализаторов
class mainClass var no1 : Int // local storage init(no1 : Int) self.no1 = no1 // initialization > > class subClass : mainClass var no2 : Int init(no1 : Int, no2 : Int) self.no2 = no2 super.init(no1:no1) > // Requires only one parameter for convenient method override convenience init(no1: Int) self.init(no1:no1, no2:0) > > let res = mainClass(no1: 20) let print = subClass(no1: 30, no2: 50) print("res is: \(res.no1)") print("res is: \(print.no1)") print("res is: \(print.no2)")
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
res is: 20 res is: 30 res is: 50
Инициализация наследования и переопределение
Swift 4 не позволяет своим подклассам наследовать инициализаторы суперкласса для их типов членов по умолчанию. Наследование применимо к инициализаторам суперкласса только в некоторой степени, что будет обсуждаться в разделе «Автоматическое наследование инициализатора».
Когда пользователю необходимо определить инициализаторы в суперклассе, подкласс с инициализаторами должен быть определен пользователем как пользовательская реализация. Когда переопределение должно выполняться подклассом, необходимо объявить ключевое слово «переопределение» суперкласса.
class sides var corners = 4 var description: String return "\(corners) sides" > > let rectangle = sides() print("Rectangle: \(rectangle.description)") class pentagon: sides override init() super.init() corners = 5 > > let bicycle = pentagon() print("Pentagon: \(bicycle.description)")
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
Rectangle: 4 sides Pentagon: 5 sides
Назначенные и удобные инициализаторы в действии
class Planet var name: String init(name: String) self.name = name > convenience init() self.init(name: "[No Planets]") > > let plName = Planet(name: "Mercury") print("Planet name is: \(plName.name)") let noplName = Planet() print("No Planets like that: \(noplName.name)") class planets: Planet var count: Int init(name: String, count: Int) self.count = count super.init(name: name) > override convenience init(name: String) self.init(name: name, count: 1) > >
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
Planet name is: Mercury No Planets like that: [No Planets]
Сбой инициализатора
Пользователь должен получать уведомления о любых ошибках инициализатора при определении значений класса, структуры или перечисления. Инициализация переменных иногда становится неудачной из-за
- Неверные значения параметров.
- Отсутствие необходимого внешнего источника.
- Условие, предотвращающее успешную инициализацию.
Для отлова исключений, генерируемых методом инициализации, Swift 4 производит гибкую инициализацию, называемую «сбойный инициализатор», чтобы уведомить пользователя о том, что что-то осталось незамеченным при инициализации структуры, класса или членов перечисления. Ключевое слово, чтобы поймать сбой инициализатора – «init?». Кроме того, инициализируемые и неисправные инициализаторы не могут быть определены с одинаковыми типами параметров и именами.
struct studrecord let stname: String init?(stname: String) if stname.isEmpty return nil > self.stname = stname > > let stmark = studrecord(stname: "Swing") if let name = stmark print("Student name is specified") > let blankname = studrecord(stname: "") if blankname == nil print("Student name is left blank") >
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
Student name is specified Student name is left blank
Сбойные инициализаторы для перечислений
Язык Swift 4 обеспечивает гибкость, позволяющую использовать инициализаторы Failable для перечислений, чтобы уведомить пользователя, когда члены перечисления не имеют инициализирующих значений.
enum functions case a, b, c, d init?(funct: String) switch funct case "one": self = .a case "two": self = .b case "three": self = .c case "four": self = .d default: return nil > > > let result = functions(funct: "two") if result != nil print("With In Block Two") > let badresult = functions(funct: "five") if badresult == nil print("Block Does Not Exist") >
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
With In Block Two Block Does Not Exist
Сбойные инициализаторы для классов
Сбойный инициализатор при объявлении с перечислениями и структурами предупреждает об ошибке инициализации при любых обстоятельствах в его реализации. Однако неисправный инициализатор в классах будет предупреждать об ошибке только после того, как для сохраненных свойств будет установлено начальное значение.
class studrecord let studname: String! init?(studname: String) self.studname = studname if studname.isEmpty return nil > > > if let stname = studrecord(studname: "Failable Initializers") print("Module is \(stname.studname)") >
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
Module is Optional("Failable Initializers")
Переопределение сбойного инициализатора
Как и в случае инициализации, у пользователя также есть возможность переопределить инициализируемый суперклассом инициализатор внутри подкласса. Отказавшая инициализацию суперкласса также может быть переопределена в нерасширяемом инициализаторе подкласса.
Инициализатор подкласса не может делегировать вплоть до инициализатора суперкласса при переопределении сбойной инициализатора суперкласса с необратимой инициализацией подкласса.
Неисправный инициализатор никогда не может делегировать отказавшему инициализатору.
Приведенная ниже программа описывает сбойные и не сбойные инициализаторы.
class Planet var name: String init(name: String) self.name = name > convenience init() self.init(name: "[No Planets]") > > let plName = Planet(name: "Mercury") print("Planet name is: \(plName.name)") let noplName = Planet() print("No Planets like that: \(noplName.name)") class planets: Planet var count: Int init(name: String, count: Int) self.count = count super.init(name: name) > override convenience init(name: String) self.init(name: name, count: 1) > >
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
Planet name is: Mercury No Planets like that: [No Planets]
Посвящение! Сбойный инициализатор
Swift 4 предоставляет ‘init?’ определить необязательный экземпляр неисправного инициализатора. Чтобы определить неявно развернутый необязательный экземпляр определенного типа ‘init!’ указан.
struct studrecord let stname: String init!(stname: String) if stname.isEmpty return nil > self.stname = stname > > let stmark = studrecord(stname: "Swing") if let name = stmark print("Student name is specified") > let blankname = studrecord(stname: "") if blankname == nil print("Student name is left blank") >
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
Student name is specified Student name is left blank
Требуемые инициализаторы
Чтобы объявить каждый подкласс инициализируемого ключевого слова ‘required’, необходимо определить его перед функцией init ().
class classA required init() var a = 10 print(a) > > class classB: classA required init() var b = 30 print(b) > > let res = classA() let print = classB()
Когда мы запускаем вышеуказанную программу, используя площадку, мы получаем следующий результат –
Swift — Инициализация
Инициализация — это процесс подготовки экземпляра класса, структуры или перечисления к использованию. Этот процесс включает установку начального значения для каждого сохраненного свойства в этом экземпляре и выполнение любых других настроек или инициализации, которые требуются до того, как новый экземпляр будет готов к использованию.
Вы реализуете этот процесс инициализации , определяя инициализаторы , которые похожи на специальные методы, которые могут быть вызваны для создания нового экземпляра определенного типа. В отличие от инициализаторов 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
Настройка инициализации
Вы можете настроить процесс инициализации с помощью входных параметров и необязательных типов свойств или путем назначения постоянных свойств во время инициализации, как описано в следующих разделах.
Параметры инициализации
Вы можете предоставить параметры инициализации как часть определения инициализатора, чтобы определить типы и имена значений, которые настраивают процесс инициализации. Параметры инициализации имеют те же возможности и синтаксис, что и параметры функций и методов.
В следующем примере определяется структура с названием 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
Вы можете инициализировать приведенную 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)
Удобные инициализаторы написаны в том же стиле, но с convenience модификатором, помещенным перед init ключевым словом, разделенным пробелом:
convenience init(parameters)
Делегирование инициализатора для типов классов
Чтобы упростить отношения между назначенными и удобными инициализаторами, 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"
Swift с нуля: Запуск и объявление иницииализатора
В предыдущих выпусках Swift с нуля, мы создали работающее приложение to-do. И вы уже могли полюбить созданную модель данных. В этом уроке мы собираемся реорганизовать эту модель данных, реализуя собственный класс модели.
1. Модель данных
Модель данных, которую мы собираемся реализовать, включает два класса: класс Task и класс ToDo , который наследуется из класса Task . Пока мы создаем и внедряем эти классы моделей, мы продолжаем наше изучение объектно-ориентированного программирования в Swift. В этом уроке мы рассмотрим инициализацию экземпляров класса и принципы наследования ролей во время инициализации.
Task Class
Начнем с реализации класса Task . Создайте новый файл Swift, выбрав New > File . в меню File в Xcode. Выберите Swift File в разделе iOS> Source. Назовите файл Task.swift и нажмите Create.
Основная реализация краткая и простая. Класс Task наследует NSObject , определенный в Foundation Framework, и свойство переменной name типа String . Класс определяет два инициализатора, init и init(name 🙂 . Есть несколько нюансов, которые могут вас сбить с толку, поэтому позвольте мне объяснить всё подробнее. Есть несколько нюансов, которые могут вас сбить с толку, поэтому позвольте мне объяснить всё подробнее.
import Foundation
class Task: NSObject
var name: String
convenience override init()
self.init(name: "New Task")
init(name: String)
self.name = name
Поскольку метод init также определен в классе NSObjectclass , нам нужно добавить префикс инициализатора с помощью ключевого слова override . В предыдущих сериях мы рассмотрели методы переопределения. В методе init мы вызываем метод init(name:) , передавая в «New Task» значение параметра name .
Метод init(name:) — это еще один инициализатор, принимающий параметр name типа String . В этом инициализаторе значение параметра name присваивается свойству name . Это достаточно легко понять. Правильно?
Инициализаторы Designated и Convenience
Что происходит с ключевым словом convenience , под префиксом метода init ? Классы могут иметь два типа инициализаторов, Designated инициализаторы и инициализаторы Convenience. Инициализаторы Convenience имеют префикс ключевого слова Convenience , что подразумевает, что init(name:) является Designated инициализатором. Почему так происходит? В чем разница между Designated и Convenience инициализаторами?
Designated инициализаторы полностью инициализируют экземпляр класса, что означает, что каждое свойство экземпляра имеет начальное значение после инициализации. Например, глядя на класс Task , мы видим, что свойство name задано со значением параметра name инициализатора init(name:) . Результат после инициализации — это полностью инициализированный экземпляр Task .
Однако инициализаторы Convenience полагаются на Designated инициализатор для создания экземпляра класса. Вот почему инициализатор init класса Task вызывает init(name:) в своей реализации. Этот процесс называется делегированием инициализатора. init делегирует к назначенному инициализатору для создания класса Task .
Инициализаторы Convenience необязательны. Не каждый класс имеет инициализатор Convenience. Designated инициализаторы обязательны, и класс должен иметь по крайней мере один Designated инициализатор для создания экземпляра.
Протокол NSCoding
Реализация класса Task еще не завершена. Далее в этой статье мы создадим массив ToDo на диске. Это возможно только в том случае, если экземпляры класса ToDo могут быть закодированы и декодированы.
Не волнуйтесь, это не ракетостроение. Нам всего лишь нужно сделать классы Task и ToDo совместимыми с NSCodingprotocol . Поэтому класс Task наследует форму класса NSObject , поскольку протокол NSCoding может быть реализован только классами, наследующими — прямо или косвенно — из NSObject . Подобно классу NSObject , протокол NSCoding определён в Foundation framework.
Принятие протокола — это то, что мы уже рассмотрели в этой серии, но есть несколько ньюансов, о которых я хочу указать. Начнем с того, что мы сообщаем компилятору, о том что класс Task соответствует протоколу NSCoding .
class Task: NSObject, NSCoding
var name: String
Новости
Все базовые концепции программирования и основы синтаксиса объясняются доступным языком, поэтому если вы никогда раньше не занимались разработкой, то эта книга — отличный старт. Теория чередуется с практическими примерами и кодом — так вы сразу сможете связать абстрактные понятия с реальными ситуациями. В каждой главе вас ждут тесты и домашние задания, которые помогут закрепить материал.
Инициализаторы и деинициализаторы
Инициализатор (конструктор) — это специальный метод, выполняющий подготовительные действия при создании экземпляра объектного типа данных. Инициализатор срабатывает при создании экземпляра, а при его удалении вызывается деинициализатор.
27.1. Инициализаторы
Инициализатор выполняет установку начальных значений хранимых свойств и различных настроек, которые нужны для использования экземпляра.
Назначенные инициализаторы
При реализации собственных типов данных во многих случаях не требуется создавать собственный инициализатор, так как классы и структуры имеют встроенные инициализаторы:
— классы имеют пустой встроенный инициализатор init()<>;
— структуры имеют встроенный инициализатор, принимающий в качестве входных аргументов значения всех свойств.
Пустой инициализатор срабатывает без ошибок только в том случае, если у класса отсутствуют свойства или у каждого свойства указано значение по умолчанию.
Для опциональных типов данных значение по умолчанию указывать не требуется, оно соответствует nil.
Инициализаторы класса и структуры, производящие установку значений свойств, называются назначенными (designated). Вы можете разработать произвольное количество назначенных инициализаторов с отличающимся набором параметров в пределах одного объектного типа. При этом должен существовать хотя бы один назначенный инициализатор, производящий установку значений всех свойств (если они существуют), и один из назначенных инициализаторов должен обязательно вызываться при создании экземпляра. Назначенный инициализатор не может вызывать другой назначенный инициализатор, то есть использование конструкции self.init() запрещено.
Инициализаторы наследуются от суперкласса к подклассу.
Единственный инициализатор, который может вызывать назначенный инициализатор, — это инициализатор производного класса, вызывающий инициализатор родительского класса для установки значений наследуемых свойств. Об этом мы говорили довольно подробно, когда изучали наследование.
Инициализатор может устанавливать значения констант.
Внутри инициализатора необходимо установить значения свойств класса или структуры, чтобы к концу его работы все свойства имели значения (опционалы могут соответствовать nil).
Вспомогательные инициализаторы
Помимо назначенных, в Swift существуют вспомогательные (convenience) инициализаторы. Они являются вторичными и поддерживающими. Вы можете определить вспомогательный инициализатор для проведения настроек и обязательного вызова одного из назначенных инициализаторов. Вспомогательные инициализаторы не являются обязательными для их реализации в типе. Создавайте их, если это обеспечивает наиболее рациональный путь решения поставленной задачи.
Синтаксис объявления вспомогательных инициализаторов не слишком отличается от синтаксиса назначенных.
СИНТАКСИС
Вспомогательный инициализатор объявляется с помощью модификатора convenience, за которым следует ключевое слово init. Данный тип инициализатора также может принимать входные аргументы и устанавливать значения для свойств.
В теле инициализатора обязательно должен находиться вызов одного из назначенных инициализаторов.
Вернемся к иерархии определенных ранее классов Quadruped, Dog и NoisyDog. Давайте перепишем класс Dog таким образом, чтобы при установке он давал возможность выводить на консоль произвольный текст. Для этого создадим вспомогательный инициализатор, принимающий на входе значение для наследуемого свойства type (листинг 27.1).
В результате при создании нового экземпляра класса Dog вам будет предложено выбрать один из двух инициализаторов: init() или init(text:). Вспомогательный инициализатор вызывает назначенный и выводит текст на консоль.
Вспомогательный инициализатор может вызывать назначенный через другой вспомогательный инициализатор.
Наследование инициализаторов
Наследование инициализаторов отличается от наследования обычных методов суперкласса. Есть два важнейших правила, которые необходимо помнить:
— Если подкласс имеет собственный назначенный инициализатор, то инициализаторы родительского класса не наследуются.
— Если подкласс переопределяет все назначенные инициализаторы суперкласса, то он наследует и все его вспомогательные инициализаторы.
Отношения между инициализаторами
В вопросах отношений между инициализаторами Swift соблюдает следующие правила:
— Назначенный инициализатор подкласса должен вызвать назначенный инициализатор суперкласса.
— Вспомогательный инициализатор должен вызвать назначенный инициализатор того же объектного типа.
— Вспомогательный инициализатор в конечном счете должен вызвать назначенный инициализатор.
На рис. 27.1. представлены все три правила
Здесь изображен суперкласс с одним назначенным и двумя вспомогательными инициализаторами. Один из вспомогательных инициализаторов вызывает другой, который, в свою очередь, вызывает назначенный. Также изображен подкласс, имеющий два собственных назначенных инициализатора и один вспомогательный.
Вызов любого инициализатора из изображенных должен в итоге вызывать назначенный инициализатор суперкласса (левый верхний блок).
С полным содержанием статьи можно ознакомиться на сайте «Хабрахабр»: https://habr.com/ru/company/piter/blog/534380/
Комментарии: 0
Пока нет комментариев