Існуючі відповіді розповідають лише половину convenience
історії. Інша половина історії, половина, на яку жодна з існуючих відповідей не відповідає, відповідає на питання, яке Десмонд опублікував у коментарях:
Чому Свіфт змусив би мене поставити convenience
перед ініціалізатором тільки тому, що мені потрібно зателефонувати self.init
з нього? "
Я трохи торкнувся її у цій відповіді , в якій детально висвітлюю кілька правил ініціалізатора Swift, але головна увага приділялася цьому required
слову. Але ця відповідь все ж стосувалася чогось, що має значення для цього питання та цієї відповіді. Ми повинні зрозуміти, як працює спадкування ініціалізатора Swift.
Оскільки Swift не дозволяє неініціалізовані змінні, вам не гарантується успадкувати всі (або будь-які) ініціалізатори з класу, з якого ви успадковуєте. Якщо ми підкласи і додамо будь-які неініціалізовані змінні екземпляра до нашого підкласу, ми перестали успадковувати ініціалізатори. І поки ми не додамо власні ініціалізатори, компілятор буде кричати на нас.
Щоб було зрозуміло, неініціалізована змінна інстанція - це будь-яка змінна інстанція, якій не присвоєно значення за замовчуванням (маючи на увазі, що необов'язкові та неявно розгорнуті необов'язкові параметри автоматично приймають значення за замовчуванням nil
).
Отже, у цьому випадку:
class Foo {
var a: Int
}
a
є неініціалізованою змінною екземпляра. Це не буде компілюватися, якщо ми не задамо a
значення за замовчуванням:
class Foo {
var a: Int = 0
}
або a
ініціалізувати методом ініціалізатора:
class Foo {
var a: Int
init(a: Int) {
self.a = a
}
}
Тепер, давайте подивимося, що станеться, якщо ми підкласимо Foo
, чи не так?
class Bar: Foo {
var b: Int
init(a: Int, b: Int) {
self.b = b
super.init(a: a)
}
}
Правильно? Ми додали змінну, і ми додали ініціалізатор, щоб встановити значення, щоб b
воно компілювалося. В залежності від того, яку мову ви звідки, можна було б очікувати , що Bar
успадкував Foo
ініціалізатор «s, init(a: Int)
. Але це не так. І як це могло? Як Foo
«s init(a: Int)
знають , як привласнити значення до b
змінної , яка Bar
додається? Це не так. Тому ми не можемо ініціалізувати Bar
екземпляр з ініціалізатором, який не може ініціалізувати всі наші значення.
Яке відношення має до цього convenience
?
Що ж, давайте розглянемо правила успадкування ініціалізатора :
Правило 1
Якщо ваш підклас не визначає жодних призначених ініціалізаторів, він автоматично успадковує всі його ініціалізатори, призначені для суперкласу.
Правило 2
Якщо ваш підклас забезпечує реалізацію всіх своїх ініціалізаторів, призначених для суперкласу - або успадковуючи їх відповідно до правила 1, або надаючи користувацьку реалізацію як частину свого визначення - він автоматично успадковує всі ініціалізатори зручності суперкласу.
Помітьте правило 2, де згадуються зручні ініціалізатори.
Отже, що робитьconvenience
ключове слово - це вказувати нам, які ініціалізатори можуть бути успадковані підкласами, які додають змінні екземпляра без значень за замовчуванням.
Візьмемо цей Base
клас прикладу :
class Base {
let a: Int
let b: Int
init(a: Int, b: Int) {
self.a = a
self.b = b
}
convenience init() {
self.init(a: 0, b: 0)
}
convenience init(a: Int) {
self.init(a: a, b: 0)
}
convenience init(b: Int) {
self.init(a: 0, b: b)
}
}
Зауважте, у нас є три convenience
ініціалізатори. Це означає, що у нас є три ініціалізатори, які можна успадкувати. І у нас є один призначений ініціалізатор (призначений ініціалізатор - це просто будь-який ініціалізатор, який не є ініціалізатором зручності).
Ми можемо створити екземпляри базового класу чотирма різними способами:
Отже, давайте створимо підклас.
class NonInheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
}
Ми передаємо у спадок від Base
. Ми додали власну змінну екземпляра і не надали їй значення за замовчуванням, тому ми повинні додати власні ініціалізатори. Ми додали один, init(a: Int, b: Int, c: Int)
, але це не відповідає підпису Base
класу позначається ініціалізатор: init(a: Int, b: Int)
. Це означає, що ми не успадковуємо жодні ініціалізатори з Base
:
Отже, що буде, якщо ми отримали у спадок Base
, але ми продовжили і реалізували ініціалізатор, який відповідав призначеному ініціалізатору Base
?
class Inheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
convenience override init(a: Int, b: Int) {
self.init(a: a, b: b, c: 0)
}
}
Тепер, окрім двох ініціалізаторів, які ми реалізували безпосередньо в цьому класі, оскільки ми реалізували призначений для ініціалізатора відповідний Base
клас ініціалізатора, ми дістаємося до успадкування всіх ініціалізаторів Base
класу convenience
:
Той факт, що ініціалізатор із відповідним підписом позначений як convenience
не має жодного значення. Це означає лише, що Inheritor
має лише один призначений ініціалізатор. Отже, якщо ми успадковуємо з Inheritor
, нам просто доведеться реалізувати цей призначений ініціалізатор, а потім ми успадкуємо Inheritor
ініціалізатор зручності, що, в свою чергу, означає, що ми реалізували всі Base
призначені ініціалізатори і можемо успадкувати його convenience
ініціалізатори.