Існуючі відповіді розповідають лише половину 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ініціалізатори.