Ответ 1
Существующие ответы показывают только половину истории convenience
. Другая половина истории, половина, на которую не распространяется ни один из существующих ответов, отвечает на вопрос, который Десмонд опубликовал в комментариях:
Почему Swift заставил меня поставить
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
, init(a: Int)
. Но это не так. И как это могло быть? Как Foo
init(a: Int)
знает, как назначить значение переменной b
, которую добавил Bar
? Это не так. Поэтому мы не можем инициализировать экземпляр Bar
с инициализатором, который не может инициализировать все наши значения.
Какое отношение это имеет к convenience
?
Хорошо, давайте рассмотрим правила наследования инициализатора:
Правило 1
Если ваш подкласс не определяет какие-либо назначенные инициализаторы, он автоматически наследует все его инициализаторы, назначенные суперклассам.
Правило 2
Если ваш подкласс обеспечивает реализацию всех его инициализаторов, назначенных суперклассам, либо путем наследования их в соответствии с правилом 1, либо путем предоставления пользовательской реализации как части своего определения, то он автоматически наследует все инициализаторы удобства суперкласса.
Уведомление Правило 2, в котором упоминаются инициализаторы удобства.
То, что делает ключевое слово convenience
, указывает нам, какие инициализаторы могут быть наследованы подклассами, которые добавляют переменные экземпляра без значений по умолчанию.
Возьмем этот пример Base
class:
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
class convenience
:
Тот факт, что инициализатор с соответствующей сигнатурой отмечен как convenience
, здесь не имеет никакого значения. Это означает, что Inheritor
имеет только один назначенный инициализатор. Поэтому, если мы наследуем от Inheritor
, нам просто нужно реализовать этот назначенный инициализатор, а затем мы наследуем Inheritor
инициализатор удобства, который, в свою очередь, означает, что мы реализовали все Base
назначенные инициализаторы и можем наследует его инициализаторы convenience
.