为什么在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") } }
现有的答案只能说明convenience
故事的一半。 故事的另一半是现有答案的一半,这回答了Desmond在评论中提出的问题:
为什么Swift迫使我仅仅因为需要调用
self.init
而在我的初始化函数之前放置convenience
?
我在这个答案中略微提到了这个问题 ,其中我详细地介绍了 Swift的初始化规则中的几个 ,但是主要关注的是required
单词。 但是,答案仍然是解决与这个问题和这个答案有关的东西。 我们必须了解Swift初始化器的inheritance是如何工作的。
因为Swift不允许使用未初始化的variables,所以不能保证从inheritance的类inheritance所有(或任何)初始化器。 如果我们inheritance和添加任何未初始化的实例variables到我们的子类,我们已经停止了inheritance初始化。 直到我们添加了自己的初始化器,编译器才会对我们大喊大叫。
要清楚的是,未初始化的实例variables是没有给定默认值的任何实例variables(请记住,可选项和隐式展开的选项会自动采用默认值nil
)。
所以在这种情况下:
class Foo { var a: Int }
a
是未初始化的实例variables。 除非我们给出一个默认值,否则这将不会被编译:
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) } }
对? 我们添加了一个variables,并且我们添加了一个初始化程序来为b
设置一个值,以便编译。 根据你来自哪种语言,你可能会认为Bar
inheritance了Foo
的初始化方法init(a: Int)
。 但事实并非如此。 怎么可能? Foo
的init(a: Int)
如何知道如何为Bar
添加的b
variables赋值? 它不。 所以我们不能初始化带有初始化器的Bar
实例,它不能初始化所有的值。
这与convenience
有什么关系?
那么,让我们看一下初始化器inheritance的规则 :
规则1
如果你的子类没有定义任何指定的初始值设定项,它将自动inheritance它的所有超类指定的初始值设定项。
规则2
如果你的子类提供了它的所有超类指定的初始化符的实现,或者按照规则1inheritance它们,或者提供一个自定义实现作为其定义的一部分,那么它将自动inheritance所有的超类方便初始化符。
注意规则2,其中提到了方便初始值设定项。
那么,关键字的作用就是向我们指出哪些初始值设定项可以被添加实例variables而没有默认值的子类inheritance 。
让我们来看这个例子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
初始值设定项 这意味着我们有三个可以inheritance的初始化器。 我们有一个指定的初始值设定项(一个指定的初始值设定项是简单的初始值设定项,它不是一个方便的初始化项)。
我们可以用四种不同的方式实例化基类:
那么,我们来创build一个子类。
class NonInheritor: Base { let c: Int init(a: Int, b: Int, c: Int) { self.c = c super.init(a: a, b: b) } }
我们从Base
inheritance。 我们添加了自己的实例variables,并没有给它一个默认值,所以我们必须添加我们自己的初始化器。 我们添加了一个init(a: Int, b: Int, c: Int)
,但它不匹配Base
类指定初始化程序的签名: init(a: Int, b: Int)
。 这意味着,我们不是从Base
inheritance任何初始化器:
那么,如果我们从Base
inheritance了,会发生什么,但是我们继续实现了一个与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
类的指定初始化器,所以我们inheritance了所有的Base
类的convenience
初始化器:
具有匹配签名的初始化器被标记为convenience
的事实在这里没有区别。 这只意味着Inheritor
只有一个指定的初始化器。 所以如果我们从Inheritor
,我们只需要实现一个指定的初始化器,然后inheritanceInheritor
的便利初始化器,这意味着我们已经实现了所有Base
的指定初始化器,并且可以inheritance它的convenience
初始化器。
大多数清晰度。 从你第二个例子来看,
init(name: String) { self.name = name }
是必需的或指定的 。 它必须初始化所有的常量和variables。 便捷初始化程序是可选的,通常可用于使初始化更容易。 例如,假设你的Person类有一个可选的variablesgender:
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()
。
那么,我首先想到的是它用于代码组织和可读性的类inheritance。 继续你的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}
使用便捷初始值设定项我可以创build一个没有值的Employee()
对象,因此这个词convenience
除了其他用户在这里解释的点是我的理解。
我强烈感觉到便捷初始值设定项和扩展项之间的联系。 对于我来说,当我想要修改(在大多数情况下简化或者简单的)初始化一个已经存在的类时,方便初始化器是最有用的。
例如,您使用的某个第三方类具有四个参数的init
,但在您的应用程序中,最后两个具有相同的值。 为了避免更多的input和代码清理,你可以定义一个只有两个参数的convenience init
,并且在里面调用self.init
和last参数,默认值是default。
根据Swift 2.1文档 , convenience
初始化器必须遵守一些特定的规则:
-
一个简单的初始化器只能在同一个类中调用初始化器,而不能在超类中调用初始化器(只能跨越,不能向上)
-
一个
convenience
初始化器必须调用链中某个指定的初始化器 -
convenience
初始值设定项在调用另一个初始值设定项之前不能更改任何属性 – 而指定的初始值设定项在调用另一个初始值设定项之前, 必须初始化当前类引入的属性。
通过使用convenience
关键字,Swift编译器知道它必须检查这些条件 – 否则它不能。
一个类可以有多个指定的初始化器。 便捷初始值设定项是必须调用相同类的指定初始值设定项的次级初始值设定项。