为什么 Swift 需要方便关键字?

由于 Swift 支持方法和初始值设定项重载,你可以将多个 init放在一起,然后使用任何你认为方便的方法:

class Person {
var name:String


init(name: String) {
self.name = name
}


init() {
self.name = "John"
}
}

那么为什么 convenience关键字会存在呢? 是什么使得下面的内容大体上更好呢?

class Person {
var name:String


init(name: String) {
self.name = name
}


convenience init() {
self.init(name: "John")
}
}
27906 次浏览

我首先想到的是,它在类继承中用于代码组织和可读性。继续您的 Person类,想象一下这样的场景

class Person{
var name: String
init(name: String){
self.name = name
}


convenience init(){
self.init(name: "Unknown")
}
}




class Employee: Person{
var salary: Double
init(name:String, salary:Double){
self.salary = salary
super.init(name: name)
}


override convenience init(name: String) {
self.init(name:name, salary: 0)
}
}


let employee1 = Employee() // \{\{name "Unknown"} salary 0}
let john = Employee(name: "John") // \{\{name "John"} salary 0}
let jane = Employee(name: "Jane", salary: 700) // \{\{name "Jane"} salary 700}

使用方便的初始化器,我可以创建一个没有值的 Employee()对象,因此使用了 convenience这个词

大部分都很清楚,从你的第二个例子来看,

init(name: String) {
self.name = name
}

指定。它必须初始化所有的常量和变量。方便的初始化器是可选的,通常可用于使初始化更容易。例如,假设 Person 类有一个可选变量 sex:

var gender: Gender?

哪里性别是枚举

enum Gender {
case Male, Female
}

您可以使用这样的方便的初始化器

convenience init(maleWithName: String) {
self.init(name: name)
gender = .Male
}


convenience init(femaleWithName: String) {
self.init(name: name)
gender = .Female
}

方便的初始化程序必须调用其中的 指定或必需的初始化程序。如果您的类是一个子类,那么它必须在初始化时调用 super.init()

除了其他用户在这里解释的观点之外,我还有一点理解。

我强烈地感觉到方便的初始化程序和扩展之间的联系。对我来说,当我想修改(在大多数情况下,使它简短或容易)现有类的初始化时,方便的初始化器是最有用的。

例如,您使用的一些第三方类具有四个参数的 init,但是在您的应用程序中,最后两个参数具有相同的值。为了避免更多的类型化并使代码更干净,您可以定义一个只有两个参数的 convenience init,并在其中调用 self.init,其 last To 参数为默认值。

根据 Swift 2.1文档convenience初始化程序必须遵守一些具体的规则:

  1. convenience初始化程序只能在相同的 类,不在超级类(只跨,不上)

  2. convenience初始化器必须调用指定的初始化器 在链条的某个地方

  3. convenience初始值设定项不能在它之前更改 任何属性 调用了另一个初始值设定项,而指定的初始值设定项 必须 初始化当前类引入的属性 在调用另一个初始化程序之前

通过使用 convenience关键字,Swift 编译器知道它必须检查这些条件-否则它不能。

现有的答案只讲述了 convenience故事的一半。这个故事的另一半,也就是现有答案都没有提到的那一半,回答了德斯蒙德在评论中提出的问题:

为什么 Swift 会强迫我将 convenience放在初始化程序的前面,仅仅因为我需要从它调用 self.init?`

我在 这个答案中稍微提到了它,在 这个答案中我详细介绍了 Swift 的初始化器规则的 好几个,但是主要关注的是 required单词。但是这个答案仍然是针对与这个问题和这个答案相关的东西。我们必须了解 Swift 初始化程序继承是如何工作的。

因为 Swift 不允许未初始化的变量,所以不能保证从继承的类继承所有(或任何)初始化器。如果我们继承子类并向子类中添加任何未初始化的实例变量,那么我们就停止了对初始化器的继承。除非我们添加自己的初始化器,否则编译器会对我们大喊大叫。

需要说明的是,未初始化的实例变量是任何没有默认值的实例变量(请记住,可选项和隐式展开的可选项自动假设默认值为 nil)。

因此,在这种情况下:

class Foo {
var a: Int
}

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)。但事实并非如此。怎么可能呢?Fooinit(a: Int)如何知道如何为 Bar添加的 b变量赋值?不会的。因此,我们不能使用无法初始化所有值的初始化器来初始化 Bar实例。

这些和 convenience有什么关系?

让我们看看 初始值设定项继承的规则:

规则一

如果您的子类没有定义任何指定的初始值设定项,它会自动继承它的所有超类指定的初始值设定项。

第二条

如果你的子类提供了它所有的超类指定初始化器的实现ーー要么按照规则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初始化器。这意味着我们有三个可以继承的初始化器。我们有一个指定的初始值设定项(指定的初始值设定项就是任何不是方便初始值设定项的初始值设定项)。

我们可以用四种不同的方式实例化基类的实例:

enter image description here

那么,让我们创建一个子类。

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继承 任何初始化器:

enter image description here

那么,如果我们从 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初始化器:

enter image description here

带有匹配签名的初始化器被标记为 convenience这一事实在这里没有区别。这只意味着 Inheritor只有一个指定的初始化器。因此,如果我们从 Inheritor继承,我们只需要实现一个指定的初始化器,然后我们将继承 Inheritor的方便初始化器,这反过来意味着我们已经实现了所有 Base的指定初始化器,并且可以继承它的 convenience初始化器。

一个类可以有多个指定的初始值设定项。方便初始化器是辅助初始化器,它必须调用同一类的指定初始化器。