今天在参看Floral这个项目的时候,发现了UIButton的一种扩展方式。
i发现之前一直不怎么重视convenience这个关键字,于是参阅了一些资料进行了整理。
swfit有超级严格的初始化方法。一方面,Swift 强化了 designated 初始化方法的地位。Swift 中不加修饰的 init 方法都需要在方法中保证所有非 Optional 的实例变量被赋值初始化,而在子类中也强制 (显式或者隐式地) 调用super版本的 designated 初始化,所以无论如何走何种路径,被初始化的对象总是可以完成完整的初始化的。
在上面的示例代码中,注意在init里我们可以对let的实例常量进行赋值,这是初始化方法的重要特点。在 Swift 中let声明的值是常量,无法被写入赋值,这对于构建线程安全的 API 十分有用。而因为 Swift 的init只可能被调用一次,因此在init中我们可以为常量进行赋值,而不会引起任何线程安全的问题。
与 designated 初始化方法对应的是在init前加上convenience关键字的初始化方法。这类方法是 Swift 初始化方法中的 “二等公民”,只作为补充和提供使用上的方便。所有的convenience初始化方法都必须调用同一个类中的 designated 初始化完成设置,另外convenience的初始化方法是不能被子类重写或者是从子类中以super的方式被调用的。
只要在子类中实现重写了父类convenience方法所需要的init方法的话,我们在子类中就也可以使用父类的convenience初始化方法了。比如在上面的代码中,我们在ClassB里实现了init(num: Int)的重写。这样,即使在ClassB中没有bigNum版本的convenience init(bigNum: Bool),我们仍然还是可以用这个方法来完成子类初始化:
因此进行一下总结,可以看到初始化方法永远遵循以下两个原则:
初始化路径必须保证对象完全初始化,这可以通过调用本类型的 designated 初始化方法来得到保证;
子类的 designated 初始化方法必须调用父类的 designated 方法,以保证父类也完成初始化。
对于某些我们希望子类中一定实现的 designated 初始化方法,我们可以通过添加required关键字进行限制,强制子类对这个方法重写实现。这样做的最大的好处是可以保证依赖于某个 designated 初始化方法的convenience一直可以被使用。一个现成的例子就是上面的 init(bigNum: Bool):如果我们希望这个初始化方法对于子类一定可用,那么应当将init(num: Int)声明为必须,这样我们在子类中调用init(bigNum: Bool)时就始终能够找到一条完全初始化的路径了
另外需要说明的是,其实不仅仅是对 designated 初始化方法,对于 convenience 的初始化方法,我们也可以加上required以确保子类对其进行实现。这在要求子类不直接使用父类中的 convenience 初始化方法时会非常有帮助。
其实以上的知识点无非就是个便利制造器链的知识,我们可以做如下归纳:
init链:
为了简化指定构造器和便利构造器之间的调用关系,Swift 采用以下三条规则来限制构造器之间的代理调用:
规则 1
designated必须调用其直接父类的的指定构造器。
规则 2
convenience必须调用同一类中定义的其它构造器。
规则 3
convenience必须最终以调用一个指定构造器结束。
一个更方便记忆的方法是:
designated必须总是向上代理
convenience必须总是横向代理