首页 > 代码库 > Swift难点-继承中的构造规则实例详解

Swift难点-继承中的构造规则实例详解

关于继承中的构造规则是一个难点。如果有问题,请留言问我。
我的Swift入门教程专栏


为什么要有构造器:为类中自身和继承来的存储属性赋初值。
一、两种构造器-指定构造器和便利构造器
指定构造器:类中必备的构造器,为所有的属性赋初值。(有些子类可能不需要显示声明,因为默认从基类继承了)
便利构造器:类中的辅助构造器,通过调用指定构造器为属性赋初值。(仅在必要的时候声明)
举例
class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}
便利构造器通过convenience关键字声明,可以看到,便利构造器是通过调用指定构造器来进行构造的。这也就是一个关键的概念:横向代理。
何为代理:就是让别人帮你干活
二、构造过程中的规则
(一)构造器链就是调用构造器的顺序
规则如下:
1.1、指定构造器必须调用其父类的指定构造器
1.2、便利构造器必须调用同一类中的指定构造器
1.3、便利构造器必须最后以调用一个指定构造器而结束

总得来说一句话:便利构造器横向代理,指定构造器向上代理。
举个例子:
class Base{
    var baseVar:String
    init(baseInput:String){
        baseVar = baseInput
    }
    convenience init(){
        self.init(baseInput:"")
    }
}
class Sub:Base{
    var subVar:String;
    init(subInput:String,baseInput:String){
        subVar = subInput
        super.init(baseInput:baseInput)//这里是规则1.1
    }
    convenience init(conSubInput:String){
        self.init(subInput:conSubInput,baseInput:"")//这里是规则1.2
    }
    convenience init(){
        self.init(conSubInput:"")//这里是规则1.3,因为调用了另外一个便利构造器,而另外一个便利构造器以调用指定构造器结束
    }
}

(二)关于构造器的继承与重载
swift中,子类不会默认继承父类的构造器。
构造器的重载遵循构造器链的规则(1.1-1.3)
构造器的继承规则如下:
2.1、如果子类中没有定义任何指定构造器,将会自动继承所有父类的指定构造器
2.2、如果子类中提供了所有父类指定构造器,不管是通过规则2.1继承来的,还是自定义实现的,它将继承所有父类的便利构造器。

注意:子类可以通过部分满足规则2.2的方式,使用子类便利构造器来实现父类的指定构造器
例子一:
class Base{
    var baseVar:String
    init(baseInput:String){
        baseVar = baseInput
    }
    convenience init(){
        self.init(baseInput:"basevar")
    }
}
class Sub:Base{
    var subVar:String = "subvar";
}
这里子类没有定义任何构造器,所以满足规则2.1,2.1,将继承所有父类的指定构造器和便利构造器
所以可以这么调用
var instance1 = Sub()
var instance2 = Sub(baseInput:"newBaseVar")

例子二
class Base{
    var baseVar:String
    init(baseInput:String){
        baseVar = baseInput
    }
    init(firstPart:String,secondPart:String){
        baseVar = firstPart + secondPart
    }
    convenience init(){
        self.init(baseInput:"basevar")
    }
}
class Sub:Base{
    var subVar:String;
    init(subInput:String,baseInput:String){
        subVar = subInput
        super.init(baseInput)
    }
}

这里,子类只是实现了父类的一个构造器,所以并未继承便利构造器,也没有继承另外一个指定构造器
只可以这么创造实例
var instance = Sub(subInput:"subvar",baseInput:"basevar")

(三)基于上述两个规则,构造过程分为两个部分
阶段一
  • 某个指定的构造器或者便利构造器被调用;
  • 完成新实例的内存分配(此时内存尚未初始化);
  • 指定构造器确保其引入的所有存储属性已经赋值(存储属性极其所属内存完成初始化);
  • 指定构造器调用父类构造器(父类构造器属性初始化);
  • 这个调用父类的构造器沿着构造器链一直向上,直到最顶部。(确保所有的继承的基类过程都已经初始化)。
阶段二
  • 从顶部一直向下,每个构造器链中类指定的构造器都有机会进一步定制实例,构造器此时可以访问self,修改它的属性并且调用实例方法等等。
  • 最终,任意构造器的便利构造器将有机会定制实例和使用self。
可能这个规则有点抽象,举个例子就明白了
class Base{
    var baseVar:String
    init(baseInput:String){
        baseVar = baseInput
    }
}
class Sub:Base{
    var subVar:String;
    func subPrint(){
        println("现在可以调用实例方法了")
    }
    init(subInput:String,baseInput:String){
        subVar = subInput
        super.init(baseInput:baseInput)
//这里就完成了阶段一
        self.subVar = subInput + "123"//此时可以调用self
        subPrint()//此时也可以调用实例方法了
    }
}
总得来说:当类的实例的内存被初始化完成,也就是调用super.init()之后,就完成了阶段一了。

三、编译器的安全检查
检查一

  指定构造器必须在它所在类的属性先初始化完成后才能把构造任务向上代理给父类中的构造器。简单来说,就是先初始化自己的存储属性,在调用父类的super.init向上初始化
检查二
  指定构造器必须先向上调用父类构造器,在为继承来的属性赋初值。这个很简答,假设继承来个x,你先为x赋值为1了,而在调用父类构造器,父类构造器会为x赋另外一个初值来保证初始化过程完成,那么你赋值的1就被覆盖了
检查三
  便利构造器先调用同类中其他构造器,再为任意属性赋初值。和检查二类似,也是防止被覆盖
检查四
  构造器在第一阶段完成之前,不能饮用self,不能调用任何实例属性,不能调用实例方法


四、总结一下
指定构造器的过程是这样的
1、为自己的属性赋初值
2、调用基类构造器(super.init)
3、然后就可以调用self,和实例方法,存储属性。定制新的值了。

然后,我们看下官方文档里给出的一个比较好的例子
class Food {
    var name: String
        init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}
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)
    }
}
class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
    var output = "\(quantity) x \(name.lowercaseString)" {
        output += purchased ? " YES" : " NO"
        return output
    }
}
这个构造器链的关系如图

解释
  • 基类Food定义了一个指定构造函数和一个便利构造器
  • 子类RecipeIngredient实现了基类Food所有的指定构造器,所以它继承了基类的便利构造器
  • 子类ShoppingListItem没有定义构造器,所以继承了基类RecipeIngredient的所有构造器。Swift入门系列15-继承中的构造规则(难点)

Swift难点-继承中的构造规则实例详解