1. 引言

中文版 Apple 官方 Swift 教程已有构造过程,毕竟是官方文档,其构造过程章节非常的详尽,一次将所有的构造机制都写了,但是这会导致首次阅读是很难完全消化,阅读完后感觉没读一样。如果配合实践的话,该章节读完还是要花点时间的。本篇是记录 Swift 中类的构造规则的笔记,用于交流和巩固,值类型的构造规则不在本篇讨论。

2. 两段式构造

直接上强度。

如果已经阅读过 swift 构造过程章节,会知道 swift 的两段式构造和我们了解的其他面向对象的构造过程是不一样的。对此,我们可以对比来看。不过先介绍两个 swift 的概念。

2.1 指定构造器和便利构造器

2.1.1 概念

一般情况下,我们在设计构造方法时,会有一个参数列表非常详尽的方法,也会有参数列表比较短的方法,因为这会很方便的构造对象。用OC举例,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface Person : NSObject

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age height:(CGFloat)height gender:(BOOL)isMale;
- (instancetype)initWithName:(NSString *)name;
- (instancetype)initWithName:(NSString *)name gender:(BOOL)isMale;
@end
@implementation Person
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age height:(CGFloat)height gender:(BOOL)isMale {
return self;
}
- (instancetype)initWithName:(NSString *)name {
return [self initWithName:name age:18 height:180.f gender:YES];
}
- (instancetype)initWithName:(NSString *)name gender:(BOOL)isMale {
return [self initWithName:name age:18 height:180.f gender:isMale];
}
@end

上面第一个构造方法参数列表非常详细,后面两个构造函数参数列表短非常便捷,其在实现的时候调用了第一个构造函数,并对部分参数提供了默认值。

对与上面的两种构造函数在 Swift 中,有专门的概念。

指定构造器:初始化类中提供的所有属性。

便利构造器是:调用同一个类中的指定构造器,并为部分形参提供默认值。

2.1.2 语法

构造器的语法很简单。

便利构造器也采用相同样式的写法,但需要在 init 关键字之前放置 convenience 关键字即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
var weight: Int?
var age: Int

init(_ name: String, age: Int, height:Float, gender:Bool) {
self.age = age
}

convenience init (_ name: String) {
// 便利构造器需要调用指定构造器
self.init(name, age: 18, height: 180, gender: true)
}

convenience init(_ name: String, gender:Bool) {
self.init(name, age: 18, height: 180, gender: gender)
}
}

2.1.3 调用规则

Swift 构造器之间的代理调用遵循以下两条规则:(官方是三条,这里把第二条和第一条合并)

a. 指定构造器的实现必须调用其直接父类的的指定构造器,除非没有父类。

b. 便利构造器必须调用同类中定义的其它构造器。并且最后必须调用指定构造器。一般情况下都是调用一次指定构造器。

注意这里的直接父类,后面的父类包括构造器继承、重写等都是指的直接父类,父类的父类不算。具体原因后面继承的时候解释。

2.2 两段式构造

2.2.1 概念

官方定义:Swift 中类的构造过程包含两个阶段。第一个阶段,类中的每个存储型属性赋一个初始值。当每个存储型属性的初始值被赋值后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步自定义它们的存储型属性。

上述两个规则在官方文档中有如下的拆解:

阶段 1

  • 类的某个指定构造器或便利构造器被调用。
  • 完成类的新实例内存的分配,但此时内存还没有被初始化。
  • 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
  • 指定构造器切换到父类的构造器,对其存储属性完成相同的任务。
  • 这个过程沿着类的继承链一直往上执行,直到到达继承链的最顶部。
  • 当到达了继承链最顶部,而且继承链的最后一个类已确保所有的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成。

阶段 2

  • 从继承链顶部往下,继承链中每个类的指定构造器都有机会进一步自定义实例。构造器此时可以访问 self、修改它的属性并调用实例方法等等。
  • 最终,继承链中任意的便利构造器有机会自定义实例和使用 self

总结一下就是

a. 指定构造器中必须先初始化自己的属性,然后再调用父类的构造方法。在 OC 或其他语言中,我们一般先调用父类的构造函数,然后在初始化属性。例如C++中因为对象模型的原因是先调用父类的构造函数。

b. 在第一阶段时只能通过 self 去赋值,父类的构造函数调用之后第一阶段结束。才能访问 self 关键字。比如调用方法,访问属性。因为第一阶段完成之后才能说这个内存已经完成初始化,才能使用实例。

2.2.2 举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Person {
var weight: Int?
var age: Int

init(_ name: String, age: Int, height:Float, gender:Bool) {
// 由于 weight 是可选类型,它将自动赋值为nil,我们可以不关注
// Person 类没有父类,不需要向上代理
self.age = age
}

convenience init (_ name: String) {
self.init(name, age: 18, height: 180, gender: true)
}

convenience init(_ name: String, gender:Bool) {
self.init(name, age: 18, height: 180, gender: gender)
}
}


class Student: Person {
var studentID: Int

init(name: String, _ studentID: Int) {
self.studentID = studentID
super.init(name, age: 18, height: 180, gender: true)
// 第一阶段结束
// 可以访问self
self.talk()
}

func talk() {
let msg = String(format: "i'm %@, my number is %d", self.name!, self.studentID)
print(msg)
}

}

3. 构造器的继承和重写

3.1 自动继承

Swift 中,如果子类新增的属性都提供了默认值,那则满足如下规则之一即可自动继承父类的构造函数。

规则 1 针对父类的指定构造器

如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。因为都有默认值,因此无需再写指定构造函数。此时就可以继承父类的构造函数

规则 2 针对父类的便利构造器

如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承父类所有的便利构造器。

总结一下:

a. 如果子类新增的属性都提供了默认值,那么无需再写任何指定构造器,它将继承它父类的所有构造方法。

b. 子类可以通过重写、或者将父类的指定构造器加上一个 convenience 关键字变成便利构造器来完成对父类所有指定构造器的实现,这样就可以自动继承父类所有的便利构造器。

即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。

3.2 继承与重写

通过上面的自动继承规则可以看到自动继承的前提条件是新增的属性需要有默认值,然后满足一定的规则才会触发自动继承。

因此在Swift中我们一般都是通过重写、实现便利构造器等来提供一个或多个跟父类相同的构造器。

在重写时,我们需要加上 override 关键字。

1
2
3
4
5
6
7
class Teacher: Person {
var subject: String
override init(_ name: String, age: Int, height: Float, gender: Bool) {
self.subject = "语文"
super.init(name, age: age, height: height, gender: gender)
}
}

请理解以下说明:

由于自动触发继承父类的指定构造器,那么该类有父类的素有构造器。如果该类自定义实现了指定构造器,它无法再继承父类的指定构造器。因此 Swift 的所有的重写与继承都是与直接父类关联的,与父类的父类与隔断的。

4. 结尾

类的继承规则如上所述,通过实践与观察其实理解后就变得容易。总结下来就是 Swift 的的构造继承规则比较严谨,因此一般都是和直接父类打交道。其他变成语言如C++、OC、Java等父类的父类都会继承。

可失败构造器、必要构造器、值类型构造规则等在官方文档有介绍,其概念比较简单,很容易上手这里不做记录。

瑞思拜~。