在swift中,对象的初始化和oc中类似,分为两个阶段,一个阶段是对象的内存分配,另一个阶段是对对象中的store value进行初始化。不过第一个阶段和oc略有不同,oc在第一阶段分配内存后,会将所有的属性设为0或者null,而swift则更为灵活,可以指定一些除0以外的值。
全能初始化方法和便利初始化方法
在详细分析两段式(two-phrase)初始化之前,先了解一下swift中两种初始化方法:全能初始化方法(designated initializier)和便利初始化方法(convenience initializer)。
全能初始化方法
就像其名字一样,通过全能初始化方法来初始化一个对象后,其所有的store property都能够进行初始化。一个类可以有多个全能初始化方法,但是一般来说只有一个。只要保证全能初始化方法能够初始化所有属性即可。
class GameCharacter {
var weapon: String
init() {
self.weapon = "fist"
}
init(weapon: String) {
self.weapon = weapon
}
}
如上,Person类有两个全能初始化方法,一个init()
,一个是init(name: String)
,每个初始化方法都能够将name进行初始化。
如果不在初始化方法中对非可选值的属性进行初始化,编译器会提示未初始化的错误。
便利初始化方法
便利初始化方法则只是初始化类中部分属性。看以下代码
class Warrior: GameCharacter {
var shield: String
init(weapon: String, shield: String) {
self.shield = shield
super.init(weapon: weapon)
}
convenience init(shield: String) {
self.init(weapon: "fist", shield: shield)
}
}
Warrior有一个全能初始化方法init(weapon: String, shield: String
,这个初始化方法可以初始化所有的属性,也就是shield
和从父类继承来的weapon
。另外还有一个便利初始化方法init(shield: String)
,该方法只显示指定了shield的值,而weapon的值则是提供一个默认的值。我们注意到在这个初始化方法在前面加了一个关键字convenience
。这个convenience除了表明这个方法是便利初始化方法外,还有一个作用就是让编译器进行一些错误检查。
全能初始化方法和便利初始化方法之间的调用规则
全能初始化方法和便利初始化方法调用规则有三个
- 全能初始化方法必须调用父类的全能初始化方法
- 便利初始化方法只能调用当前类的初始化方法,不能调用父类的初始化方法
- 便利初始化方法,最终必须调用当前类的全能初始化方法。
下图可以很直观的表明这三个规则:
类的两段式初始化
swift和oc最大的区别,除了多了很多新的特性,比如新的枚举类型,struct类型等,最明显的区别就是在编译器上面。swift的编译器在编译期间做了很多的安全检查工作,比如对于一个可能为nil的值,必须进行解包;两个不同类型的变量不能相互进行赋值(即使是Double和Int之间的也不能隐式转换)。类的初始化方法也一样。
在类的初始化方法中,有四个安全性的检查:
- 全能初始化方法必须确保在父类被初始化之前,所有的属性被正确初始化
- 再给被继承的属性赋值之前,必须调用父类的全能初始化方法,也就是说,父类必须被初始化之后才能给被集成的属性赋值
- 便利初始化方法在对属性赋值之前,必须调用其它初始化方法
- 在当前类没有被正确初始化之前(即第一阶段),不能调用类的实例方法和读取实例属性
下面这段代码说明了这四个检查
class Warrior: GameCharacter {
var shield: String
init(weapon: String, shield: String) {
self.shield = shield // 在调用父类之前,必须对初始化本类的属性
// attack() 编译器错误,当前类还没初始化完成,不能调用实例方法
super.init(weapon: weapon)
self.weapon = "sword" //在父类初始化完成后,才能对从父类集成的属性进行赋值
}
convenience init(shield: String) {
self.init(weapon: "fist", shield: shield)
self.shield = "silver shield" //便利初始化方法在调用其它初始化方法后,才能对属性进行赋值
}
func attack() {
Swift.print("use \(weapon) to attack")
}
}
如果以上四个检查任何一个不符合,编译器都会报错。
而swift的两段式初始化正式基于这四个安全检查的,下面来看看这两个阶段分别作了什么
第一阶段
- 调用全能初始化方法或者便利初始化方法,系统分配内存,但是并未对这个类进行初始化
- 在全能初始化方法中初始化所有属性
- 调用父类的全能初始化方法执行前两个相同的步骤
- 沿着继承链不断向上一直到根类为止
- 这个时候在继承链上的类都被正确的初始化,第一阶段完成
第二阶段
- 从根类开始向下返回,每个类的全能初始化方法都能够进行更多的对类的初始化操作,包括可以使用属性,可以调用实例方法
- 最后,任何便利方法都有机会去对类进行更多的初始化
沿用上一个代码片段,当调用init(weapon: String, shield: String)
时,第一阶段的初始化就开始了,直到GameCharacter的全能初始化方法中的self.weapon = "fist"
,这时候第一阶段结束。然后再沿着继承链向下,直到Warrior类的全能初始化方法中的self.weapon = "sword
时,第二阶段结束。
我们可以看到,swift的二段式初始化能够保证我们在使用一个类的时候能够被正确的初始化,而且这个过程都是在编译时检查的,这样就能够确保我们在编写类的初始化代码的时候不会犯错。