Существующие ответы рассказывают только половину convenience
истории. Другая половина истории, та, которую не охватывает ни один из существующих ответов, отвечает на вопрос, который Десмонд разместил в комментариях:
Зачем Swift заставляет меня ставить convenience
перед инициализатором только потому, что мне нужно с него позвонить self.init
?
Я прикоснулся на нее чуть - чуть в этом ответе , в котором я охватывать несколько правил инициализатора Свифта в деталях, но основное внимание было на 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
«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
инициализаторы.