15.2 构造器继承

我们在第14章介绍过构造与析构,在一个实例的构造过程中会调用构造器这样一个特殊的方法。在构造器中可以使用构造器代理帮助完成部分构造工作。类构造器代理分为横向代理和向上代理,横向代理只能在发生在同一类内部,这种构造器称为便利构造器。向上代理发生在继承的情况下,在子类构造过程中,要先调用父类构造器初始化父类的存储属性,这种构造器称为指定构造器。

第14章只介绍了便利构造器调用,本节将介绍向上代理和指定构造器调用。

15.2.1 构造器调用规则

我们先看看上一节的PersonStudent类示例,修改代码如下:

  1. class Person {
  2. var name : String
  3. var age : Int
  4. func description() -> String {
  5. return "\(name) 年龄是: \(age)"
  6. }
  7. convenience init () {
  8. self.init(name: "Tony")
  9. self.age = 18
  10. }
  11. convenience init (name : String) {
  12. self.init(name: name, age: 18)
  13. }
  14. init (name : String, age : Int) {
  15. self.name = name
  16. self.age = age
  17. }
  18. }
  19. class Student : Person {
  20. var school : String
  21. init (name : String, age : Int, school : String) {
  22. self.school = school
  23. super.init(name : name, age : age)
  24. }
  25. convenience override init (name : String, age : Int) {
  26. self.init(name : name, age : age, school : "清华大学")
  27. }
  28. }
  29. let student = Student()
  30. 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. 安全检查1

指定构造器必须保证其所在类的所有存储属性都初始化完成,之后才能向上调用父类构造器代理。

示例代码如下:

  1. class Student : Person {
  2. var school : String
  3. init (name : String, age : Int, school : String) { ①
  4. self.school = school ②
  5. super.init(name : name, age : age) ③
  6. }
  7. convenience override init (name : String, age : Int) {
  8. self.init(name : name, age : age, school : "清华大学")
  9. }
  10. }

上述代码第①行和第④行是定义构造器,其中第②行代码是初始化school属性,它一定要在第③行super.init(name : name, age : age)语句之前调用,super.init(name : name, age : age)是向上调用父类构造器代理。如果我们把第②行和第③行的代码互换一下,会出现如下编译错误:

  1. error: property 'self.school' not initialized at super.init call
  2. super.init(name : name, age : age)
  3. ^

使用两段式构造过程分析上述代码,语句①~③都还属于第一阶段。

  1. 安全检查2

指定构造器必须先向上调用父类构造器代理,然后再为继承的属性设置新值,否则指定构造器赋予的新值将被父类中的构造器所覆盖。

示例代码如下:

  1. class Student : Person {
  2. var school : String
  3. init (name : String, age : Int, school : String) {
  4. self.school = school
  5. super.init(name : name, age : age)
  6. self.name = "Tom"
  7. self.age = 28
  8. }
  9. convenience override init (name : String, age : Int) {
  10. self.init(name : name, age : age, school : "清华大学")
  11. }
  12. }

上述代码第①行定义指定构造器init (name : String, age : Int, school : String),第②行代码初始化school属性,这条语句必须要放在向上调用父类构造器代理之前(见第③行代码super.init(name : name, age : age))。安全检查2关键是检查第④行和第⑤行代码,这两行代码是为从父类继承下来的nameage属性赋值,这要在向上调用父类构造器代理之后进行。

使用两段式构造过程分析上述init (name : String, age : Int, school : String)构造器,语句②和③属于第一阶段,语句④和⑤属于第二阶段。

  1. 安全检查3

便利构造器必须先调用同一类中的其他构造器代理,然后再为任意属性赋新值,否则便利构造器赋予的新值将被同一类中其他指定构造器所覆盖。

示例代码如下:

  1. class Student : Person {
  2. var school : String
  3. ......
  4. convenience override init (name : String, age : Int) {
  5. self.init(name : name, age : age, school : "清华大学")
  6. self.name = "Tom"
  7. }
  8. }

上述代码第①行定义了便利构造器init (name : String, age : Int),其中第②行代码是调用同一类中的其他构造器代理,首先调用语句self.init(name : name, age : age, school : "清华大学"),然后再调用第③行的self.name = "Tom"语句为school属性赋值。

  1. 安全检查4

构造器在第一阶段构造完成之前,不能调用实例方法,也不能读取实例属性。

15.2.3 构造器继承

Swift中的子类构造器的来源有两种:自己编写和从父类继承。并不是父类的所有的构造器都能自动继承下来,能够从父类自动继承下来的构造器是有条件的,如下所示。 条件1:如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。 条件2:如果子类提供了所有父类指定构造器的实现,无论是通过条件1继承过来的,还是通过自己编写实现的,它都将自动继承所有父类的便利构造器。

下面看示例代码:

  1. class Person {
  2. var name : String
  3. var age : Int
  4. func description() -> String {
  5. return "\(name) 年龄是: \(age)"
  6. }
  7. convenience init () {
  8. self.init(name: "Tony")
  9. self.age = 18
  10. }
  11. convenience init (name : String) {
  12. self.init(name: name, age: 18)
  13. }
  14. init (name : String, age : Int) {
  15. self.name = name
  16. self.age = age
  17. }
  18. }
  19. class Student : Person {
  20. var school : String
  21. init (name : String, age : Int, school : String) {
  22. self.school = school
  23. super.init(name : name, age : age)
  24. }
  25. convenience override init (name : String, age : Int) {
  26. self.init(name : name, age : age, school : "清华大学")
  27. }
  28. }
  29. class Graduate : Student {
  30. var special : String = ""
  31. }

上述代码第①行、第⑤行和第⑧行分别定义了PersonStudentGraduate,它们是继承关系。

我们先看看符合条件1的继承,Graduate继承StudentGraduate类没有定义任何指定构造器,它将自动继承所有Student的指定构造器,如图15-3所示,符合条件1后,GraduateStudent继承了如下指定构造器:

  1. init (name : String, age : Int, school : String)

我们再看符合条件2的继承,由于Graduate实现了Student的所有指定构造器,Graduate将自动继承所有Student的便利构造器。如图15-3所示,符合条件2后,GraduateStudent继承了如下3个便利构造器:

  1. init (name : String, age : Int)
  2. init (name : String)
  3. 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类实现了父类指定构造器,因此也继承了父类的另外两个便利构造器(见代码第②行和第③行)。