初始化方法顺序
与 Objective-C 不同,Swift 的初始化方法需要保证类型的所有属性都被初始化。所以初始化方法的调用顺序就很有讲究。在某个类的子类中,初始化方法里语句的顺序并不是随意的,我们需要保证在当前子类实例的成员初始化完成后才能调用父类的初始化方法。
class Cat {
var name: String
init() {
name = "cat"
}
}
class Tiger: Cat {
let power: Int
override init() {
power = 10
super.init()
name = "tiger"
}
}
一般来说,子类的初始化顺序是:
- 设置子类自己需要初始化的参数,
power = 10
- 调用父类的相应的初始化方法,
super.init()
- 对父类中的需要改变的成员进行设定,
name = "tiger"
如果在子类中不需要对父类的成员做出改变的话就不存在第 3 步。这种情况下,第2步的super.init()
也不需要手动调用了,Swift可以自动的帮我们完成。于是上面的代码可以改成如下的形式:
class Cat {
var name: String
init() {
name = "cat"
}
}
class Tiger: Cat {
let power: Int
override init() {
power = 10
// 如果我们不需要改变 name 的话,
// 虽然我们没有显式地对 super.init() 进行调用
// 不过由于这是初始化的最后了,Swift 替我们自动完成了
}
}
DESIGNATED,CONVENIENCE 和 REQUIRED
在Objective-C中,init方法是非常不安全的,因为没有人能保证init方法只被调用一次,也没有人保证在初始化方法调用以后实例的各个变量都完成初始化,甚至如果在初始化里使用属性进行设置的话,还可能会造成各种问题
所以 Swift 有了超级严格的初始化方法。一方面,Swift 强化了 designated 初始化方法的地位。Swift 中不加修饰的 init 方法都需要在方法中保证所有非 Optional 的实例变量被赋值初始化,而在子类中也强制 (显式或者隐式地) 调用 super 版本的 designated 初始化,所以无论如何走何种路径,被初始化的对象总是可以完成完整的初始化的。
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
/*
只要在子类中实现重写了父类convenience方法所需要的init方法的话,
我们在子类中就也可以使用父类的convenience初始化方法了。
*/
override init(num: Int) {
numB = num + 1
super.init(num: num)
}
}
let bObj = ClassB(bigNum: true)
bObj.numA // 1000
bObj.numB // 1001
从上面的代码可以看出,init方法可以对let变量进行赋值操作,这是初始化方法最重要的特点。因为 Swift 的 init 只可能被调用一次,因此在 init 中我们可以为常量进行赋值,而不会引起任何线程安全的问题。
convenience
关键字修饰的init方法是用于补充和提供使用上的方便。所有的 convenience 初始化方法都必须调用同一个类中的 designated 初始化完成设置,另外 convenience 的初始化方法是不能被子类重写或者是从子类中以 super 的方式被调用的。
对于某些我们希望子类中一定实现的 designated 初始化方法,我们可以通过添加 required
关键字进行限制,强制子类对这个方法重写实现。
这样做的最大的好处是可以保证依赖于某个 designated 初始化方法的 convenience 一直可以被使用。
如果我们不使用required
关键字来限制的话,那么下面的ClassC这个类就无法调用父类中的init(bigNum: Bool)
方法
class ClassC: ClassB {
let numC: Int
init(cNum value: Int) {
numC = value
super.init(num: 100)
}
}
// 这里已经不能调用ClassA中的convenience初始化方法
let cObj = ClassC(cNum: 10)
cObj.numA // 100
cObj.numB // 101
cObj.numC // 10
另外需要说明的是,其实不仅仅是对 designated 初始化方法,对于 convenience 的初始化方法,我们也可以加上 required 以确保子类对其进行实现。这在要求子类不直接使用父类中的 convenience 初始化方法时会非常有帮助。