我们在深入
了解初始化
方法之前,不妨先再想想Swift中
的初始化想要达到一种怎样的目的
。目的其实就是安全
。在Objective-C
中,init 方法
是非常不安全
的:没有人能保证init 只被调用一次
,也没有人保证在初始化方法
调用以后实例的各个变量
都完成初始化
,甚至如果在初始化
里使用属性进行设置
的话,还可能会造成各种问题
,虽然Apple
也明确说明了不应该
在 init
中使用属性
来访问,但是这并不是
编译器强制
的,因此还是会有很多开发者犯这样的错误。
所以,Swift
有了超级严格
的初始化
方法。一方面,Swift强化
了 designated初始化
方法的地位
。Swift中不加修饰
的 init 方法
都需要在方法中保证
所有非Optional
的实例变量被赋值初始化
,而在子类
中也强制
(显式或者隐式的)调用 super
版本的 designated 初始化
,所以无论走何种路径,被初始化的对象总是可以完成完整的初始化
的。
class ClassA {
let numA: Int
init(num: Int) {
numA = num
}
}
class ClassB: ClassA {
let numB: Int
override init(num: Int) {
numB = num + 1
super.init(num: num)
}
}
在上面的实例代码中,注意在init
里我们可以对 let
的实例常量进行赋值
,这是初始化方法
的重要特点
。在Swift中let
声明的值是不变量
,无法
被写入赋值
,这对于构建线程安全
的API十分有用。而因为 Swift
的 init 只可能
被调用一次
,因此在 init
中我们可以为不变量
进行赋值
,而不会引起
任何线程安全
的问题。
与designated 初始化
方法对应的是在 init 前
加上 convenience 关键字
的初始化方法。这类方法是 Swift 初始化
方法中的“二等公民”
,只作为补充
和提供使用上的方便
。所有的 convenience 初始化
方法都必须
调用同一个类中的 designated 初始化
完成设置,另外 convenience
的初始化方法是不能被子类重写
的,也不能
从子类中以 super 的方式
被调用
。
class ClassA {
let numA: Int
init(num: Int) {
numA = num
}
convenience init(bigNum: Bool) {
self.init(num: bigNum ? 1000 : 1)
}
}
class ClassB: ClassA {
let numB: Int
override init(num: Int) {
numB = num + 1
super.init(num: num)
}
}
只要在子类
中实现
重写了父类 convenience 方法
所需要的init
方法,我们在子类
中就也可以
使用父类的 convenience 初始化方法
了。比如在上面的代码中,我们在 ClassB
里实现了 init(num: Int)
的重写。这样,即使在 ClassB
中没有 bigBum 版本
的convenience init(bigNum: Bool)
,我们仍然可以
用这个方法
来完成子类初始化
:
let anObj = ClassB.init(bigNum: true)
print("\(anObj.numA), \(anObj.numB)") // numA = 1000, numB = 1001
进行总结一下,可以看到初始化方法永远遵循以下两个原则:
初始化
路径必须保证
对象完全初始化
,这可以通过调用本类型
的 designated 初始化方法
来得到保证
。子类
的designated
初始化方法必须
调用父类
的designated
方法,以保证父类
也完成初始化
。对于某些我们希望子类中一定实现的 designated 初始化方法,我们可以通过添加 required 关键字进行限制,强制子类对这个方法重写实现。这样的一个最大的好处是可以保证依赖于某个 designated 初始化方法的 convenience 一直可以被使用。一个现成的例子就是上面的 init(bigNum: Bool),如果我们希望这个初始化方法对于子类一定可用,那么应当将 init(num: Int) 声明为“必须”,这样我们在子类中调用 init(bigNum: Bool) 时就始终能够找到一条完全初始化的路径了:
class ClassA {
let numA: Int
required init(num: Int) {
numA = num
}
convenience init(bigNum: Bool) {
self.init(num: bigNum ? 1000 : 1)
}
}
class ClassB: ClassA {
let numB: Int
required init(num: Int) {
numB = num + 1
super.init(num: num)
}
}
另外需要说明的是,其实不仅仅
是对 designated 初始化
方法,对于 convenience 初始化
方法, 我们也可以
加上 required
以确保子类
对其的实现
。这在要求子类不直接使用父类
中的 convenience 初始化
方法时会非常有用
。
class ClassA {
let numA: Int
required init(num: Int) {
numA = num
}
convenience required init(bigNum: Bool) {
self.init(num: bigNum ? 1000 : 1)
}
}
class ClassB: ClassA {
let numB: Int
required init(num: Int) {
numB = num + 1
super.init(num: num)
}
}