文章目录
  1. 1. 1、类的指定构造器和便利构造器
    1. 1.1. 1.1、指定构造器(Designated Initializers)
    2. 1.2. 1.2、便利构造器(Convenience Initializers)
  2. 2. 2、类的构造器代理
    1. 2.1. 2.1、构造器代理的规则
    2. 2.2. 2.2、类构造过程的两个阶段
      1. 2.2.1. 阶段一
      2. 2.2.2. 阶段二
    3. 2.3. 2.3、构造器的继承和重写
    4. 2.4. 2.4、构造器的自动继承

1、类的指定构造器和便利构造器

类的构造器和结构体的构造器一样,是对自己类中定义的存储属性进行初始化。为了继承关系中把所有继承关系的类中的存储属性都保证初始化,Swift提供了2种不同类型的构造器来保证初始化行为。

1.1、指定构造器(Designated Initializers)

  • 指定构造器是类中主要构造器,每个类必须保证有一个以上的构造器(包括默认的指定构造器)。
  • 指定构造器负责初始化本身的存储属性,并且调用父类构造器,由父类来继续初始化过程。
  • 指定构造器可以通过继承而来。这部分介绍,在文章后面可以看到。
  • 指定构造器的语法跟普通的构造器语法一样,不加任何其他修饰:
1
2
3
init(parameters) {
statements
}

1.2、便利构造器(Convenience Initializers)

  • 便利构造器是次要的,支持型的构造器,类可以不需要便利构造器,也可以定义多个。
  • 便利构造器可以调用自身类中的指定构造器,用来设置一些默认值,也可以用作一些特殊用途。
  • 便利构造器语法是在init前面加上修饰符convenience:
1
2
3
convenience init(parameters) {
statements
}

2、类的构造器代理

2.1、构造器代理的规则

  • 一个指定构造器必须调用自身直接父类的一个指定构造器
  • 一个便利构造器必须调用本类中另外一个构造器
  • 便利构造器最终必须调用一个本类的指定构造器

简单点说,指定构造器必须向上代理,便利构造器必须横向代理

我们来看看官方的图例,更加具体的说明这个构造器代理的规则。

类的构造器代理1

上图中,父类定义了1个指定构造器和2个代理构造器。2个便利构造器都调用了其他的构造器,这符合规则2,且其中一个便利构造器最终调用了唯一的指定构造器,这符合规则3。子类定义了2个指定构造器和1个便利构造器,其中2个指定构造器调用了父类的指定构造器,这符合规则1,而子类中唯一的便利构造器调用了指定构造器,这符合规则2,3。

让我们来看一个更加复杂的例子,大家可以看图,自行脑补和规则相对应:

类的构造器代理2

2.2、类构造过程的两个阶段

  • 第一阶段:每个类中的存储属性都确保被定义自己的类初始化
  • 第二阶段:每个类都有机会修改自己定义的存储属性的值

二个阶段的过程确保了存储属性在初始化后才被调用,并且确保存储属性不被其他的构造器修改。Swift为了确保这二个阶段的顺利完成,主要做了以下四个安全性检查:

  • 每个类中的指定构造器在调用父类的指定构造器之前,需要把自己定义的存储属性做初始化操作。

  • 指定构造器如果需要赋值继承下来的存储属性,必须在赋值之前,调用父类的指定构造器。否则,赋值的值将被父类的指定构造器的赋值覆盖掉。

  • 便利构造器如果需要赋值给一个存储属性(包括父类或者本类的),必须在赋值之前,调用另外一个构造器,否则,赋值的值将会被本类的指定构造器的赋值覆盖掉。
  • 当第一阶段结束以后,所有的构造器都可以访问实例方法,读取任何实例属性,获取调用self

根据这四个安全性检查,每个阶段需要做的事情如下:

阶段一

  • 类调用指定构造器或者便利构造器
  • 分配类的一个新的实例内存,内存这个时候还没有被初始化
  • 指定构造器确保所有的本类定义的存储属性都被赋值,这些存储属性对应的内存也就都被初始化了
  • 指定构造器调用父类的指定构造器,对父类本身的存储属性做同样的操作
  • 在继承链中,做相同的操作,直到基类为止
  • 基类的指定构造器完成对本类的存储属性赋值以后,整个继承链中,所有的存储属性都被赋值,可以认为实例的内存都被初始化了,阶段一完成

阶段二

  • 从基类往下,每一个类的指定构造器,都可以对本类的存储属性进行修改,并且可以通过self修改属性和调用方法等。
  • 便利构造器也可以通过self对实例进行操作

还是看看实际的例子,下图是说明阶段一要做的事情:
类构造过程的两个阶段1

首先调用一个便利构造器进行对象声明,然后便利构造器调用本类的指定构造器,做安全检查一,也就是对本类的存储属性进行初始化,然后向上调用父类的指定构造器,父类指定构造器对本类的存储属性进行初始化,也就是做安全检查一。并且父类是基类,整个继承链到顶,阶段一完成。

下图说明阶段二要做的事情:

类构造过程的两个阶段2
也就是从基类开始,每个构造器可以对自己类的初始化过程进行定制。直到回到最开始的那个构造器里面去。

2.3、构造器的继承和重写

  • 与Objective-C不同,Swift中,子类不自动继承父类的构造器
  • 如果要重写父类的指定构造器,子类必须使用override关键字
  • 子类可以使用便利构造器重写父类的指定构造器,需要加上override前缀
  • 子类重写便利构造器时,不需要加override关键字

2.4、构造器的自动继承

以下条件下,子类会自动继承父类的构造器:

  • 规则一:如果子类没有定义指定构造器,子类将自动继承父类的所有指定构造器
  • 规则二:如果子类实现或者继承了所有的指定构造器,那么子类将继承所有父类的便利构造器
  • 在子类中定义其他的便利构造器,前面2个规则也成立
  • 子类可以定义一个便利构造器重写父类的指定构造器,规则二也成立

以上条件的前提是,子类的存储属性都有默认的值。

我们举例来说明,2.3和2.4的一些具体的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Food
{
var name: String
init(name: String)
{
self.name = name
}
convenience init()
{
self.init(name: "[unnamed]")
}
}
let namedMeat = Food(name: "Bacon")
let mysteryMeat = Food()

构造器的自动继承1
上例中定义了Food类,有1个指定构造器和一个便利构造器。接下来我们定义一个子类来看看如何继承和重写构造器。

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
class RecipeIngredient: Food
{
var quantity: Int
init(name: String, quantity: Int)
{
self.quantity = quantity
super.init(name: name)
self.name = "Ingredient: " + name
}
// 可以在子类中定义便利构造器重写父类的指定构造器
convenience override init(name: String)
{
self.init(name: name, quantity: 1)
}
/*子类定义指定构造器重写父类的指定构造器
override init(name: String)
{
quantity = 1
super.init(name: "")
}*/
//子类定义便利构造器重写父类的便利构造器
convenience init()
{
self.init(name: "[unnamed]", quantity: 1)
}
}

下面,我们看看官方的例子对于自动继承的例子:

1
2
3
4
5
6
7
8
9
10
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}

构造器的自动继承2

官方的例子不像说明重写的RecipeIngredient类定义了多个重写的构造器。新的RecipeIngredient定义了2个构造器,其中一个新增指定构造器对新的存储属性quantity父类的存储属性name进行赋值,另外一个定义的便利构造器是重新了父类的指定构造器。而父类的init()便利构造器没有在子类中重写。根据自动继承的规则二,子类会自动继承父类的便利构造器init(),且这个便利构造器会根据父类便利构造器调用另外一个构造器init(name)。所以我们可以用3个构造器来声明一个RecipeIngredient对象:

1
2
3
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

接下来我们定义一个RecipeIngredient的子类ShoppingListItem

1
2
3
4
5
6
7
8
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}

由于ShoppingListItem没有定义任何构造器,所以根据规则1,2,会继承父类RecipeIngredient所有的便利构造器和指定构造器:

构造器的自动继承3

调用实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
var breakfastList = [
ShoppingListItem(),
ShoppingListItem(name: "Bacon"),
ShoppingListItem(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
print(item.description)
}
// 1 x Orange juice ✔
// 1 x Bacon ✘
// 6 x Eggs ✘
文章目录
  1. 1. 1、类的指定构造器和便利构造器
    1. 1.1. 1.1、指定构造器(Designated Initializers)
    2. 1.2. 1.2、便利构造器(Convenience Initializers)
  2. 2. 2、类的构造器代理
    1. 2.1. 2.1、构造器代理的规则
    2. 2.2. 2.2、类构造过程的两个阶段
      1. 2.2.1. 阶段一
      2. 2.2.2. 阶段二
    3. 2.3. 2.3、构造器的继承和重写
    4. 2.4. 2.4、构造器的自动继承