swift中的Initialization

本文相关知识参考了Apple官方的Swift文档

Initialization也就是我们说的构造方法,class,struct,enumeration都会通过这个方法来构造一个自己的实例对象,并在这个过程中做一些必要的初始化。但是和Objective-C不一样的是,你会发现swift中的构造方法没有返回值。他们的主要作用就是保证一个类型的实例在被第一次使用前,能够被正确的构造出来,并初始化。class类型的实例还会有析构函数(Deinitialization)。我们还是通过structclass具体来看一下他们的用法和原理吧。

struct的Initialization

无论是struct还是class,他们的实例在被构造的过程中必须要保证所有的stored properties全都被初始化成功,也就是说所有的存储属性必须都有值,否者无法被正确构造。所以基于此,常规构造方法如下:

1
2
3
4
5
6
7
8
9
struct People {
var name: String
var age: Int
init() {
name = "Peter"
age = 18
}
}
let peopleOne = People()//name = peter, age = 18

但是同样的,我们也可以为struct的存储属性设置默认值,这样它就能获得一个默认构造方法,如下:

1
2
3
4
5
struct People {
var name = "Peter"
var age = 18
}
let peopleOne = People()//name = peter, age = 18

但是很多时候为了更好的适用性,我们通常会自定义我们想要的构造方法,如下:

1
2
3
4
5
6
7
8
9
struct People {
var name: String
var age: Int
init(personName name: String, _ age: Int) {
self.name = name
self.age = age
}
}
let peopleOne = People(personName: "Peter", 18)//name = peter, age = 18

上面构造方法中的personName叫做Argument Label是在构造方法被外部调用时使用的,而它后面的name叫做Parameter Name是在构造方法内部使用的,如果希望外部调用的时候简洁一些,不要Argument Label那就可以直接用__下划线替代。

Memberwise Initializers

struct还有一个很特别的默认提供的构造方法,Memberwise Initializers,我们可以把它叫做逐一成员构造法,按照字面意思即可理解,就是将他的所有成员逐个全部赋值,只要struct没有自定义的构造方法,那么就会默认获得这个构造方法。用法如下:

1
2
3
4
5
struct People {
var name = ""
var age = 0
}
let peopleOne = People(ame: "Peter", age: 18)//name = peter, age = 18

值类型的构造器代理

什么是构造器代理(Initializer Delegation),值类型的构造器代理其实就是类型里面有不止一个构造方法,其中一个构造方法的实现里调用了本类型里的另一个构造方法,来避免一些重复的代码。class类型因为有继承的关系,构造器代理会复杂很多,我们下面会讲。直接看代码就明白了,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct People {
var name: String
var age: Int
init(personName name: String, _ age: Int) {
self.name = name
self.age = age
}

init(info: (String, Int)) {
var infoName = info.0
var infoAge = info.1
self.init(personName: infoName, infoAge)
}
}
let peopleOne = People(info: ("Peter", 18))//name = peter, age = 18

上面的代码,我们定义了另外一个构造器,入参是一个(String, Int)类型的Tuple,但是我们在它的实现里,通过代理给原有的构造方法来实现了完整构造,并节省了重复代码。

开心

class的Initialization

类类型中的构造方法比起值类型来说,要复杂一些,因为它可以继承等,我们还是通过例子从最简单的开始说起,同样的,如果类里面给所有的存储属性提供了默认值,那么它可以获得一个默认的构造方法init(),如下:

1
2
3
4
5
class Person {
var name = "Peter"
var age = 18
}
let personOne = Person()//name = Peter, age = 18

Designated Initializer和Convenience Initializer

Designated Initializer我们直译为指定构造器,这是class类型里面的主要构造器,我们平时写的构造器基本都是Designated Initializer,来看例子,如下:

1
2
3
4
5
6
7
8
9
10
11
class Person {
var name = "Peter"
var age = 18

//这就是Designated Initializer
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let personOne = Person(name: "Peter", age: 18)//name = Peter, age = 18

同时,类类型里面还有Convenience Initializer,我们直译为便利构造器,它只能横向代理,也就是说在它的实现里,最后必须调用本类里其他的指定构造器。便利构造器很多时候是为了一些特殊的场景,为了方便初始化而创造出来的,他的实现是基于某个指定构造器的,所以最终必须要调用某个指定构造器。我们看例子,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person {
var name = "Peter"
var age = 18

//这就是Designated Initializer
init(name: String, age: Int) {
self.name = name
self.age = age
}

//这就是Convenience Initializer
convenience init(info: (String, Int)) {
var infoName = info.0
var infoAge = info.1
self.init(name: infoName, age: infoAge)//调用指定构造器
}
}
let personOne = Person(info: ("Peter", 18))//name = Peter, age = 18

通过class的继承来看swift的two-phase process

继承和代理规则

上面我们看到了class类型的指定构造器和便利构造器,还是比较简单的,但是如果刚刚的Person的类型又派生出多个子类,有了继承,那就变得稍稍复杂了。我们还是来看一下吧,我们看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
var name = "Peter"
var age = 18

//这就是Designated Initializer
init(name: String, age: Int) {
self.name = name
self.age = age
}

//这就是Convenience Initializer
convenience init(info: (String, Int)) {
var infoName = info.0
var infoAge = info.1
self.init(name: infoName, age: infoAge)//调用指定构造器
}
}

class Student: Person {
var studentNumber: Int = 0
}

Student继承自Person因为没有实现任何指定构造方法,所以自动继承父类Person的指定构造方法,这是swift中继承规则的第一条,第二条是,如果派生类实现了父类的所有指定构造方法,无论是规则一继承实现还是重载实现,那么可以继承父类的所有便利构造方法。上面的例子中,Student满足了这两个条件,所以它继承了父类所有的指定和便利构造方法。我们来试试,如下:

1
2
let studentOne = Student(name: "Peter", age: 18)//Student可以调用父类的指定构造器
let studentTwo = Student(info: ("Jack", 20))//Student可以调用父类的便利构造器

当然,我们也可以不继承父类的指定构造器,我们可以直接重载它,如下:

1
2
3
4
5
6
7
8
9
class Student: Person {
var studentNumber: Int

//重载父类指定构造器
override init(name: String, age: Int) {
self.studentNumber = 0
super.init(name: name, age: age)
}
}

那么,上面的初始化studentOnestudentTwo依然是可行的,它符合swift的两条继承规则。但是重载父类指定构造器必须调用父类的指定构造器,也就是上述代码中的super.init(name: name, age: age),这个也就是我们所说的纵向代理。指定构造器只能纵向代理。

同时我们也可以重载父类的便利构造方法,但是不用写override关键字,原因是便利构造器只能横向代理,也就是只能代理给本类的指定构造器,实际上并没有调用父类的同名便利构造方法,严格意义上来讲,重载父类便利构造方法不能算作重载,所以也就不用override关键字了。来看代码:

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

//重载父类指定构造器
override init(name: String, age: Int) {
self.studentNumber = 0
super.init(name: name, age: age)
}

//‘重载’父类便利构造器,严格意义上不能称之为重载
convenience init(info: (String, Int)) {
let infoName = info.0
let infoAge = info.1
self.init(name: infoName, age: infoAge)
self.studentNumber = 8
}
}

ok,让我们少许总结一下吧

class中的继承规则

1.如果子类没有实现任何指定构造方法,那么该子类会自动继承父类的所有指定构造方法。

2.如果子类实现了父类的所有指定构造方法,无论是通过规则一继承的,还是通过重载实现的,那么子类可以继承父类的所有便利构造方法。

class的构造方法的代理规则(我们可以简称为:指定构造器必须纵向代理,便利构造器必须横向代理)

1.一个指定构造方法必须调用它直接父类的构造方法。

2.一个便利构造方法必须调用本类的其他构造方法。

3.一个便利构造方法无论调用本类多少构造方法,最终必须调用一个指定构造方法。

我们来看一张图,就十分清晰明了了,指定构造器必须纵向代理,便利构造器必须横向代理如下:

代理规则

two-phase process

two-phase process,我直译为两段式构造过程。那什么叫两段式构造过程,其实就是swift把创建一个实例类的过程分成了两步。

第一步

  • class中的一个指定构造器或便利构造器被调用

  • 这个实例的内存被分配,但是该内存还没有被初始化

  • class中的指定构造器给class中的所有存储属性赋值,并且这些存储属性的内存被初始化

  • 本类中的指定构造器调用父类的指定构造器,让父类完成和本类一样的工作初始化所有存储属性

  • 顺着继承链一直到达最顶层的父类,完成同样的操作

  • 当所有父类均对自有的存储属性完成初始化操作,保证了所有存储属性有值,那么该实例被完全初始化,第一步也就完成了

第二步

  • 从继承链的最顶层逐步往下,没个指定构造器都有机会进一步定制实例对象,现在没个构造器里可是使用self来修改属性或者调用实例方法等

  • 最终,在这条链中的便利构造器有机会去进一步自定义实例对象,并且可以使用self关键字

所以还记得我们上面举过的一个例子吗,如下:

1
2
3
4
5
6
7
8
9
class Student: Person {
var studentNumber: Int

//重载父类指定构造器
override init(name: String, age: Int) {
self.studentNumber = 0
super.init(name: name, age: age)
}
}

如果我们把上述代码指定构造器里的self.studentNumber = 0super.init(name: name, age: age)这两行对调可以吗?我们试一下,代码如下:

1
2
3
4
5
6
7
8
9
class Student: Person {
var studentNumber: Int

//重载父类指定构造器
override init(name: String, age: Int) {
super.init(name: name, age: age)//Property 'self.studentNumber' not initialized at super.init call
self.studentNumber = 0
}
}

编译器会立马报错Property 'self.studentNumber' not initialized at super.init call,为什么,因为在两段式构造过程中,第一步要求派生类必须自下而上的完成存储属性的初始化,所以上述代码必须先将本类的studentNumber先初始化,然后调用父类指定构造器,让父类做相同工作。

同样的,便利构造器也必须在最终调用完指定构造器后,才能使用self来自定义实例对象,如下:

1
2
3
4
5
6
convenience init(info: (String, Int)) {
let infoName = info.0
let infoAge = info.1
self.init(name: infoName, age: infoAge)//必须先调用完指定构造器
self.studentNumber = 8//然后才能自己自定义实例对象
}

总结

struct和class的init构造方法是我们很常用的方法,不过里面也有非常多的细节和注意的地方,希望上述相关知识在你以后使用构造方法的时候能够帮上你的忙。

Author: Uncle Peter
Link: http://yoursite.com/2017/07/04/swift中的Initialization/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.