15.2 构造器继承
我们在第14章介绍过构造与析构,在一个实例的构造过程中会调用构造器这样一个特殊的方法。在构造器中可以使用构造器代理帮助完成部分构造工作。类构造器代理分为横向代理和向上代理,横向代理只能在发生在同一类内部,这种构造器称为便利构造器。向上代理发生在继承的情况下,在子类构造过程中,要先调用父类构造器初始化父类的存储属性,这种构造器称为指定构造器。
第14章只介绍了便利构造器调用,本节将介绍向上代理和指定构造器调用。
15.2.1 构造器调用规则
我们先看看上一节的Person
和Student
类示例,修改代码如下:
class Person {
var name : String
var age : Int
func description() -> String {
return "\(name) 年龄是: \(age)"
}
convenience init () { ①
self.init(name: "Tony")
self.age = 18
}
convenience init (name : String) { ②
self.init(name: name, age: 18)
}
init (name : String, age : Int) { ③
self.name = name
self.age = age
}
}
class Student : Person {
var school : String
init (name : String, age : Int, school : String) { ④
self.school = school
super.init(name : name, age : age)
}
convenience override init (name : String, age : Int) { ⑤
self.init(name : name, age : age, school : "清华大学")
}
}
let student = Student()
println("学生: \(student.description())")
在Person
类中定义了3个构造器,见代码第①~③行,其中,第①行和第②行是便利构造器,第③行是指定构造器。在Student
类中,定义了2个构造器,见代码第④~⑤行,其中第④行是指定构造器,第⑤行是便利构造器。
构造器之间的调用形成了构造器链,如图15-1所示,我们为这个构造器添加了标号。
Swift限制构造器之间的代理调用的规则有3条,如下所示。
指定构造器必须调用其直接父类的的指定构造器。从图15-1可见,
Student
中的④号指定构造器调用Person
中的③号指定构造器。便利构造器必须调用同一类中定义的其他构造器。从图15-1可见,
Student
中的⑤号便利构造器调用同一类中的④号便利构造器,Person
中的①号便利构造器调用同一类中的②号便利构造器。便利构造器必须最终以调用一个指定构造器结束。从图15-1可见,
Student
中的⑤号便利构造器调用同一类中的④号指定构造器,Person
中的②号便利构造器调用同一类中的③号指定构造器。
图 15-1 构造器链
15.2.2 构造过程安全检查
在Swift中,类的构造过程包含两个阶段,如图15-2所示。
图 15-2 构造过程的两个阶段
第一阶段,首先分配内存,初始化子类存储属性,沿构造器链向上初始化父类存储属性,到达构造器链顶部,初始化全部的父类存储属性。
第二阶段,从顶部构造器链往下,可以对每个类进行进一步修改存储属性、调用实例方法等处理。
Swift编译器在构造过程中可以进行一些安全检查工作,这些工作可以有效地防止属性在初始化之前被访问,也可以防止属性被另外一个构造器意外地赋予不同的值。
为确保构造过程顺利完成,Swift提供了4种安全检查。
- 安全检查1
指定构造器必须保证其所在类的所有存储属性都初始化完成,之后才能向上调用父类构造器代理。
示例代码如下:
class Student : Person {
var school : String
init (name : String, age : Int, school : String) { ①
self.school = school ②
super.init(name : name, age : age) ③
}
convenience override init (name : String, age : Int) {
self.init(name : name, age : age, school : "清华大学")
}
}
上述代码第①行和第④行是定义构造器,其中第②行代码是初始化school
属性,它一定要在第③行super.init(name : name, age : age)
语句之前调用,super.init(name : name, age : age)
是向上调用父类构造器代理。如果我们把第②行和第③行的代码互换一下,会出现如下编译错误:
error: property 'self.school' not initialized at super.init call
super.init(name : name, age : age)
^
使用两段式构造过程分析上述代码,语句①~③都还属于第一阶段。
- 安全检查2
指定构造器必须先向上调用父类构造器代理,然后再为继承的属性设置新值,否则指定构造器赋予的新值将被父类中的构造器所覆盖。
示例代码如下:
class Student : Person {
var school : String
init (name : String, age : Int, school : String) { ①
self.school = school ②
super.init(name : name, age : age) ③
self.name = "Tom" ④
self.age = 28 ⑤
}
convenience override init (name : String, age : Int) {
self.init(name : name, age : age, school : "清华大学")
}
}
上述代码第①行定义指定构造器init (name : String, age : Int, school : String)
,第②行代码初始化school
属性,这条语句必须要放在向上调用父类构造器代理之前(见第③行代码super.init(name : name, age : age)
)。安全检查2关键是检查第④行和第⑤行代码,这两行代码是为从父类继承下来的name
和age
属性赋值,这要在向上调用父类构造器代理之后进行。
使用两段式构造过程分析上述init (name : String, age : Int, school : String)
构造器,语句②和③属于第一阶段,语句④和⑤属于第二阶段。
- 安全检查3
便利构造器必须先调用同一类中的其他构造器代理,然后再为任意属性赋新值,否则便利构造器赋予的新值将被同一类中其他指定构造器所覆盖。
示例代码如下:
class Student : Person {
var school : String
......
convenience override init (name : String, age : Int) { ①
self.init(name : name, age : age, school : "清华大学") ②
self.name = "Tom" ③
}
}
上述代码第①行定义了便利构造器init (name : String, age : Int)
,其中第②行代码是调用同一类中的其他构造器代理,首先调用语句self.init(name : name, age : age, school : "清华大学")
,然后再调用第③行的self.name = "Tom"
语句为school
属性赋值。
- 安全检查4
构造器在第一阶段构造完成之前,不能调用实例方法,也不能读取实例属性。
15.2.3 构造器继承
Swift中的子类构造器的来源有两种:自己编写和从父类继承。并不是父类的所有的构造器都能自动继承下来,能够从父类自动继承下来的构造器是有条件的,如下所示。 条件1:如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。 条件2:如果子类提供了所有父类指定构造器的实现,无论是通过条件1继承过来的,还是通过自己编写实现的,它都将自动继承所有父类的便利构造器。
下面看示例代码:
class Person { ①
var name : String
var age : Int
func description() -> String {
return "\(name) 年龄是: \(age)"
}
convenience init () { ②
self.init(name: "Tony")
self.age = 18
}
convenience init (name : String) { ③
self.init(name: name, age: 18)
}
init (name : String, age : Int) { ④
self.name = name
self.age = age
}
}
class Student : Person { ⑤
var school : String
init (name : String, age : Int, school : String) { ⑥
self.school = school
super.init(name : name, age : age)
}
convenience override init (name : String, age : Int) { ⑦
self.init(name : name, age : age, school : "清华大学")
}
}
class Graduate : Student { ⑧
var special : String = ""
}
上述代码第①行、第⑤行和第⑧行分别定义了Person
、Student
和Graduate
,它们是继承关系。
我们先看看符合条件1的继承,Graduate
继承Student
,Graduate
类没有定义任何指定构造器,它将自动继承所有Student
的指定构造器,如图15-3所示,符合条件1后,Graduate
从Student
继承了如下指定构造器:
init (name : String, age : Int, school : String)
我们再看符合条件2的继承,由于Graduate
实现了Student
的所有指定构造器,Graduate
将自动继承所有Student
的便利构造器。如图15-3所示,符合条件2后,Graduate
从Student
继承了如下3个便利构造器:
init (name : String, age : Int)
init (name : String)
init ()
如图15-3所示,Student
继承Person
后有4个构造器。
图 15-3 构造器自动继承(虚线表示继承过来的构造器)
条件1对Student
不起作用,因为它有指定构造器,Student
类中的便利构造器init (name : String, age : Int)
满足了条件2(见代码第⑦行),它实现了父类指定构造器init (name : String, age : Int)
(见代码第④行),由于子类构造器与父类构造器参数相同,需要使用override
关键字,表示子类构造器重写(overriding
)了父类构造器。
由于Student
类实现了父类指定构造器,因此也继承了父类的另外两个便利构造器(见代码第②行和第③行)。