Swift 5出来一段时间了,想学习一下,就决定仿写个功能丰富一点的项目,开始上手时因为构造函数经常的提示编译不通过,所以就结合官方文档和实际项目深入理解一下.如有理解不到位的地方请及时指出,感谢
项目先实现了类似头条的多VC的展示框架
通过这个demo理解Swift的构造与其他语法.对照点按照官方文档构造过程
1. 和OC的构造器不同的是 swift的构造器没有返回值
2. 存储属性的初始赋值
类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
否则编译器会告诉Property 'self.expectedWidth' not initialized at super.init call(你该计算属性在初始化之前没有得到赋值)
当你为存储型属性分配默认值或者在构造器中为设置初始值时,它们的值是被直接设置的,不会触发任何属性观察者,如下图,图中print断点是不会走入的
3. 自定义的类型有一个逻辑上允许值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时机可以赋值为空——都需要将它声明为 可选类型
4. 构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能避免多个构造器间的代码重复。
如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)。这种限制避免了在一个更复杂的构造器中做了额外的重要设置,但有人不小心使用自动生成的构造器而导致错误的情况。
5. 指定构造器和便利构造器
Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.
指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并调用合适的父类构造器让构造过程沿着父类链继续往上进行
类倾向于拥有极少的指定构造器,普遍的是一个类只拥有一个指定构造器。指定构造器像一个个“漏斗”放在构造过程发生的地方,让构造过程沿着父类链继续往上进行,是一个类初始化的必经之路
下图是UIView 的指定构造器只有两个
每一个类都必须至少拥有一个指定构造器
Swift 构造器之间的代理调用遵循以下三条规则
规则 1
指定构造器必须调用其直接父类的的指定构造器。
规则 2
便利构造器必须调用同类中定义的其它构造器。
规则 3
便利构造器最后必须调用指定构造器。
以上构造规则我理解和OC中NS_DESIGNATED_INITIALIZER使用基本一致 例如
当OC中对一个类声明了NS_DESIGNATED_INITIALIZER (中文译名 指定构造器) 相当于将自己定义的初始化方法(即Swift的便利构造器) 指定为指定构造器,此时OC 必须去重载父类的指定构造器的方法,否则会提示如下图的错误
而父类UIView 的.h中刚好用NS_DESIGNATED_INITIALIZER指定了两个初始化方法
两段式构造过程
第一个阶段,类中的每个存储型属性赋一个初始值。当每个存储型属性的初始值被赋值后
第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步自定义它们的存储型属性
目的:Swift 的编译器会对初始化的方法进行安全地检查已保证实例的初始化可以被安全正确的执行
Swift 的两段式构造过程跟 Objective-C 中的构造过程类似。最主要的区别在于阶段 1,Objective-C 给每一个属性赋值 0 或空值(比如说 0 或 nil)。Swift 的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以 0 或 nil 作为合法默认值的情况
Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程不出错地完成
安全检查 1
指定构造器必须保证它所在类的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
安全检查 2
指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
在子类的super之前想去重新赋值继承的contentCount的值会提示如下错误
下图将子类的contentCount 赋值过程在super.init()之后可以覆盖父类的值且可以访问
安全检查 3
便利构造器必须为任意属性(包括所有同类中定义的)赋新值之前代理调用其它构造器。如果没这么做,便利构造器赋予的新值将被该类的指定构造器所覆盖。
如图,创建VCContainer 实例构造了convenience 的实例方法,因为规则二(便利构造器必须调用同类中定义的其它构造器)所以之前调用super.init() 要改为self.init()
否则会报如下错误
安全检查 4
构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用 self 作为一个值。
self.init() 之前当前还处在阶段1的时期,此时还没进入到继承链向上执行,此时访问实例函数,编译器报错提示在实例初始化之前调用了self.代码如下图
将testInstance()方法放在init方法中,没有到达了继承链最顶部是依然会报同样的错误
阶段 1
类的某个指定构造器或便利构造器被调用。
完成类的新实例内存的分配,但此时内存还没有被初始化。
指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
指定构造器切换到父类的构造器,对其存储属性完成相同的任务。
这个过程沿着类的继承链一直往上执行,直到到达继承链的最顶部。
当到达了继承链最顶部,而且继承链的最后一个类已确保所有的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成。
阶段 2
从继承链顶部往下,继承链中每个类的指定构造器都有机会进一步自定义实例。构造器此时可以访问 self、修改它的属性并调用实例方法等等。
最终,继承链中任意的便利构造器有机会自定义实例和使用 self。
6. 构造器的继承和重写
跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器
在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是在重写父类的这个指定构造器。因此,你必须在定义子类构造器时带上 override 修饰符
在iOS项目随处可见如下
7.构造器的自动继承
规则 1
如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。
eg.1
如下图 子类CGVCContainerBar继承自UIView,如果没有定义任何指定构造器,会继承来自父类的两个默认指定构造器
CGVCContainerBar 重载了init(frame:)指定构造器,因此要对原两个指定构造器都做重载
init(coder aDecoder:NSCoder) 方法是来自父类的指定构造器,因为是require修饰,因此 必须要实现.
Eg.2
同理eg1,子类CGVContainer 继承自CGViewContainer ,重载了父类的Init(coder)方法,导致init(frame:)并没有被继承,所以当前类是没有指定构造器init(frame:) 此处总结点:子类中初始化方法必须覆盖全部初始化路径,以保证对象完全初始化;
解决方法1.重载init(frame:)
2.删除init(coder:) 指定构造器后,子类中没有定义的指定构造器,因此默认继承父类的构造器
规则 2
如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承父类所有的便利构造器
小结:
1.子类中初始化方法必须覆盖全部初始化路径,以保证对象完全初始化;
2.子类中指定构造器必须调用父类中对应的指定构造器,以保证父类也能完成初始化(构造规则一)
3.便利构造器必须调用同一个类中定义的其它初始化方法 (构造规则二)
4.便利构造器在最后必须调用一个指定构造器.(构造规则三)
5.指定构造器必须要确保所有被类中提到的属性在代理向上调用父类的指定构造器前被初始化, 之后才能将其它构造任务代理给父类中的构造器(阶段 一)
6.子类如果没有定义任何指定构造器,则默认继承所有父类的指定构造器(继承规则一)
7.子类中如果重写父类中便利构造器所需要的全部 init 方法,就可以在子类中使用父类的便利构造器继承规则二)