现有的答案只说明了一半convenience
。故事的另一半(现有答案中没有涵盖的另一半)回答了Desmond在评论中发布的问题:
为什么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
如果您的子类没有定义任何指定的初始化器,它将自动继承其所有超类指定的初始化器。
规则二
如果您的子类提供了其所有超类指定的初始化器的实现(通过按照规则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
初始值设定项。