现有的答案只讲述了convenience 故事的一半。故事的另一半,即现有答案都没有涵盖的那一半,回答了 Desmond 在 cmets 中发布的问题:
为什么 Swift 会强迫我将 convenience 放在初始化器前面,因为我需要从中调用 self.init?`
我在this answer 中稍微提到了它,其中我详细介绍了几个 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) 怎么知道如何给Bar 添加的b 变量赋值?它没有。所以我们不能用一个不能初始化我们所有值的初始化器来初始化一个Bar实例。
这与convenience 有什么关系?
好吧,我们来看看the rules on initializer inheritance:
规则 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 初始化器。