Swift version:5.0
初始化是什么
初始化简而言之是一个准备的过程,就好比你想吃地三鲜,这时候你光在脑海里想,你是吃不到的,你需要买菜、洗菜、切菜、炒菜,然后你才能吃上地三鲜。初始化就相当于买菜、洗菜、切菜、炒菜的过程。回到代码上面,它主要做了下面两件事:
- 给每一个存储属性赋初始值
- 执行其他必须的设置
代码示例:
除了上面的方式,我们还可以通过设置默认值的方式来给存储属性赋值。
现在,我们知道了初始化就是执行构造器的过程,下面我们来看一下默认构造器和创建自定义构造器的几种方式。
默认构造器
对于值类型和引用类型,默认构造器是不同的。如果 class 给所有的存储属性赋了默认值,且没有实现任何自定义的构造器,那么 Swift 会提供一个默认的构造器。
class
|
|
而对于 struct ,只要没有实现任何自定义构造器,不管它有没有给存储属性赋默认值, Swift 都会提供默认构造器。
struct
|
|
当你给存储属性分配默认值或者通过构造器设置初始值的时候,属性的值被直接设置,不会触发属性观察
创建自定义构造器的几种方式
形参(parameter)构造器
|
|
形参(parameter)和实参(argument)构造器
|
|
如果声明了实参名称,调用的时候不能省略实参名称。
如果你在定义构造器时没有提供实参标签,Swift 会为构造器的每个形参自动生成一个实参标签。
不带实参的形参构造器
如果你不希望构造器的某个形参使用实参标签,可以使用下划线(_)来代替显式的实参标签来重写默认行为。
有可选类型属性的构造器
|
|
有常量属性的构造器
常量属性,只能在构造器中被赋值,且一旦赋值就不可修改。子类中也不能修改。
到这里,我们了解了默认构造器和创建自定义构造器的几种方式,接下来我们看一下如果使用构造器代理( Initializer Delegation )来避免多个构造器的代码重复。
因为值类型是不能继承的,所以构造器代理又分为值类型的构造器代理和类的构造器代理,我们先看一下比较简单的值类型的构造器代理。
值类型的构造器代理
对于值类型,你可以使用 self.init
在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用 self.init
。还有就是:如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器
。这主要是为了避免在一个构造器中做了一些重要设置,但有人不小心使用自动生成的构造器而导致错误的情况。
如果你想让默认构造器、自定义的构造器都可以使用的话,你可以将自定义的构造器放在Extension
中。
通过上面的代码你可以使用init()
、init(origin: Point, size: Size)
、init(center: Point, size: Size)
三种方式来初始化 Rect 的实例。
看完上面的代码你也许会有疑问:init(origin: Point, size: Size)
和默认构造器是一样的,那为什么我们还要再写一遍?那是因为我们自定义了init(center: Point, size: Size)
构造器,所以默认构造器已经失效,我们只能再自己写一遍。
如果你不想自己写一遍默认构造器的话,可以用下面这种方式实现上面等效的代码:
类的构造器代理
为了确保实例中的所有存储属性都能有初始值, Swift 提供了两种构造器,分别是:指定构造器、便利构造器。
指定构造器( Designated Initializers )
定义一个指定构造器:
便利构造器( Convenience Initializers )
定义一个便利构造器:
为了简化指定构造器和便利构造器之间的调用关系,Swift 构造器之间的代理调用需要遵循类类型的构造器代理规则。
类类型的构造器代理规则
规则有三条,分别是:
- 指定构造器必须调用其直接父类的的指定构造器。
- 便利构造器必须调用同类中定义的其它构造器。
- 便利构造器最后必须调用指定构造器。
总结一下就是:指定构造器必须总是向上代理(去父类);便利构造器必须总是横向代理(在本类)。如下图所示:
构造器的两个阶段
Swift 中类的构造过程包含两个阶段。第一个阶段:给类中的每个存储属性赋初始值。只要每个存储属性初始值被赋值,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步自定义它们的存储属性。
Swift 通过4步安全检查来确定构造器两个阶段的成功执行:
- 安全检查1:指定构造器必须在完成本类所有存储属性赋值之后,才能向上代理到父类的构造器。1234567891011class Animal {var head = 1}class Dog: Animal {var foot: Intoverride init() {super.init()foot = 4}}
上面的super.init()
会报错,因为此时 Dog 的 foot 还没有被赋值。将 init() 改为下面即可:
安全检查2:指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器。
123456//这时,你必须显式的调用super.init(),因为你要修改继承属性- head 的值override init() {foot = 4super.init()head = 2}安全检查3:便利构造器必须先调用其他构造器,再为任意属性(包括所有同类中定义的)赋新值。
1234567convenience init(foot: Int) {//先调用其他构造器,如果此处不调用会编译出错self.init()//再为任意属性(包括所有同类中定义的)赋新值self.foot = foothead = 3}安全检查4:构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用
self
作为一个值。1234567891011121314class Dog: Animal {var foot: Intoverride init() {foot = 4super.init()head = 2// 如果上面的未完成,是不能调用run()的,因为self还没有完整的创建run()}func run() {//do something}}
现在看一下阶段一和阶段二的完整流程:
第一阶段示例图 - 自下而上:
第二阶段示例图 - 自上而下:
构造器的继承与重写
继承
默认情况下子类是不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被子类自动继承。1234567891011121314151617181920212223242526规则 1如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。规则 2如果子类提供了所有父类指定构造器的实现——无论是通过规则 1继承过来的,还是提供了自定义实现——它将自动继承父类所有的便利构造器。class Animal {let head = 1var name = ""init(name: String) {self.name = name}convenience init() {self.init(name: "animal")}}class Dog: Animal {let foot = 4}//自动继承父类所有的指定构造let d1 = Dog(name: "dog") // d1.name dog//自动继承父类所有的便利构造器let d2 = Dog() // d2.name animal重写
12345678910111213class Vehicle {var numberOfWheels = 0var description: String {return "\(numberOfWheels) wheel(s)"}}class Bicycle: Vehicle {override init() {super.init()numberOfWheels = 2}}
可失败构造器
在给构造器传入无效的形参,或缺少某种所需的外部资源,又或是不满足某种必要的条件等情况下,我们创建一个可失败的构造器是非常有必要的,来看一下可失败构造器的语法:
值类型的可失败构造器可以横向代理到自身其他的可失败构造器;类的可失败构造器既可横向代理自身的可失败构造器,亦可向上代理到父类的可失败构造器。
但无论是向上还是横向,只要可失败构造器触发构造失败,整个构造过程将即刻停止,不会再执行后面的构造代码。
必要构造器
我们可以通过required
关键字来实现必要构造器,子类必须实现父类的必要构造器。
有一点需要注意的就是:如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。
在我们日常开发中,我们会经常自定义UITableViewCell的子类来实现我们定制化的需求,如果我们没有实现required init?(coder aDecoder: NSCoder)
方法的话,我们的代码是编译报错的。查看文档我们发现该方法为NSCoding
的方法,且该方法为UIView 必要构造器,所以它的子类必须实现该方法。
练手小栗子
光说不练假把式,通过下面的几个小栗子来加深一下对构造器的理解。
分析一下下面的例子是否正确并思考错误的原因。
例1
123456789class Animal {let head = 1}class Dog: Animal {override init() {head = 3}}例2
1234567891011struct Student {var name = ""var age = 0init(stuName: String, stuAge: Int) {name = stuNameage = stuAge}}Student(name: "jack", age: 18)例3
12345678910111213141516class Animal {let head = 1}class Dog: Animal {let foot: Intinit(foot: Int) {self.foot = footrun()}func run() {//do something}}例4
1234567891011121314151617class Animal {let head = 1}class Dog: Animal {var foot: Intinit(foot: Int) {super.init()self.foot = footrun()}func run() {//do something}}
总结
通过上面的介绍,我们了解了默认构造器、自定义构造器类型、构造器代理、构造器的两个阶段、可失败构造器和必要构造器是什么。现在我们总结一下需要重点理解的构造器代理和构造器的两个阶段:
构造器代理
121、值类型只能横向代理2、类可以横向代理和向上代理构造器的两个阶段
123456781、先确保所有的存储属性都被赋予初始值2、在实例准备使用之前,可以自定义存储属性的值通过4步安全检查来确保两个阶段成功:* 安全检查1:指定构造器必须在完成本类所有存储属性赋值之后,才能向上代理到父类的构造器。* 安全检查2:指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器。* 安全检查3:便利构造器必须先调用其他构造器,再为任意属性(包括所有同类中定义的)赋新值。* 安全检查4:构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用 `self` 作为一个值。
除了上面的两个重点,我们还需要注意一下几个小点:
关于构造器,自己总结的思维导图:
好了,本文到此就结束了。希望通过这篇文章能让大家对 Swift 的构造过程能有一个更清晰的认识。Enjoy Every Day!🙂