类的继承和初始化

优质
小牛编辑
115浏览
2023-12-01

类的继承和初始化

  • 所有类的存储属性(包括从它的父类继承的所有属性)都必须在初始化期间分配初始值

  • Swift为类类型定义了两种初始化器以确保所有的存储属性接收一个初始值. 这些就是所谓的指定初始化器和便捷初始化器

  • 指定初始化器是类的主要初始化器. 指定的初始化器可以初始化所有那个类引用的属性并且条用合适的父类初始化器来继续这个初始化过程给父类链

  • 类偏向于少量初始化器, 并且一个类通常只有一个指定初始化器. 指定初始化器是初始化开始并持续初始化过程到父类链的"传送"点

  • 每个类至少得有一个指定初始化器. 如同在初始化器的自动继承里描述的那样, 在某些情况下, 这些需求通过从父类继承一个或多个指定初始化器来满足.

  • 便捷初始化器(convenience)是次要的. 你可以在相同的类里定义一个便捷初始化器来调用一个指定的初始化器作为便捷初始化器来给指定初始化器设置默认形式参数. 你也可以为具体的使用情况或输入的值类型定义一个便捷初始化器从而创建这个类的实例.

  • 如果你的类不需要便捷初始化器你可以不提供它. 在为通用的初始化模式创建快捷方式以节省时间或者类的初始化更加清晰明了的时候使用便捷初始化器

注: 最开始接触Swift 2.x的时候, 那会我对便捷初始化的称呼是: 便利构造函数, 其实一个意思

指定初始化器和便捷初始化器

  • 用于值类型的简单初始化器相同的方式来写类的指定初始化器

  • convenience修饰符放在init关键字前定义便捷初始化器

指定初始化器语法

init(parameters) {
    statements
}

便捷初始化器语法

convenience init(parameters) {
    statements
}

类的初始化委托

  • 指定初始化器必须从它的直系父类调用指定初始化器

  • 便捷初始化器必须从相同的类里调用另一个初始化器

  • 便捷初始化器最终必须调用一个指定初始化器

    类的初始化委托.png

两段式初始化

  • Swift的类初始化是一个两段式过程. 在第一个阶段, 每一个存储属性被引入类分配了一个初始值. 一旦每个存储属性的初始状态被确定, 第二个阶段就开始了, 每个类都有机会在新的实例准备使用之前来定制它的存储属性

    >

    • 指定或便捷初始化器在类中被调用
    • 为这个类的新实例分配内存. 内存还没有被初始化
    • 这个类的指定初始化器确保所有由此类引入的存储属性都有一个值. 现在这些存储属性的内存被初始化
    • 指定初始化器上交父类的初始化器为其存储属性执行相同的任务
    • 这个调用父类初始化器的过程将沿着初始化器链一直向上进行, 知道到达初始化器链的最顶部
    • 一旦到达了初始化器链的最顶部, 在链顶部的类确保所有的存储属性都有一个值, 此实例的内存被认为完全初始化了, 此时第一阶段完成

    • 从顶部初始化器往下, 链中的每一个指定初始化器都有机会进一步定制示例. 初始化器现在能够访问self并且可以修改它的属性, 调用它的实例方法等等

    • 最终, 链中任何便捷初始化器都有机会定制实例以及使用self, 此时第二阶段完成
  • 两段式初始化过程的使用让初始化更加安全, 同时在每个类的层级结构给与了完备的灵活性. 两段式初始化过程可以防止属性值在初始化期间被访问, 还可以防止属性值被另一个初始化器意外的赋予不同的值.

安全检查

  1. 指定初始化器必须保证在向上委托给父类初始化器之前, 其所在类引入的所有属性都要初始化完成.
  2. 指定初始化器必须向上委托父类初始化器, 然后才能为继承的属性设置新值. 如果不这样做, 指定初始化器富裕的新值将被父类中的初始化器锁覆盖.
  3. 便捷初始化器必须先委托同类中的其它初始化器, 然后再为任意属性赋新值(包括同类里定义的属性). 如果没这么做, 便捷初始化器赋予的新值将被自己类中其它指定初始化器所覆盖.
  4. 初始化器在第一阶段初始化完成之前, 不能调用任何实例方法、不能读取任何实例属性的值, 也不能引用self作为值.

e.g. 安全检查

  • 错误1
class Person {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    convenience init() {
        self.init(name: "unKonw", age: 0)
    }
}

class Teacher: Person {
    var salary: Int
    init(name: String, age: Int, salary: Int) {
        super.init(name: name, age: age)
        self.salary = salary
        self.name = name + "老师"
    }

    convenience init(name: String) {
        self.init(name: name, age: 30, salary: 5000)
    }

    func showInfo() -> Void {
        print("name: \(name) age: \(age) salary: \(salary)")
    }
}

这个示例违背了第1条安全检查, 错误代码:

error1.pngTeacher类中, salary是该类下新增属性, 在调用super初始化器前, 并未完成salary属性的初始化 所以需要将salary属性初始化放在调用super之前

正确的代码:

  init(name: String, age: Int, salary: Int) {
        self.salary = salary
        super.init(name: name, age: age)
  }
  • 错误2:

error2.png 其余代码部分一致, 只是将name属性初始化的位置进行了调整, nameTeacher父类的属性, 这种写法违背了安全检查第2条, 父类的属性需要在调用父类的初始化器后才能赋予新值, 所以正确写法是:

  init(name: String, age: Int, salary: Int) {
        self.salary = salary
        super.init(name: name, age: age)
        self.name = name + "老师"
    }
  • 错误3:

error3.pngTeacher的便捷初始化器下赋予name属性新值, 因为name是父类属性, 同样需要在调用父类的初始化器后才能赋予新值, 而又因为是在当前类的便利初始化器中, 需要通过调用自身的指定初始化器完成初始化 所以正确的写法是:

  convenience init(name: String) {
        self.init(name: name, age: 30, salary: 5000)
        self.name = name + "老师"
    }
  • 错误4:

error4.png 第4条, 在初始化完成前调用了实例方法 正确方式:

  init(name: String, age: Int, salary: Int) {
        self.salary = salary
        super.init(name: name, age: age)
        self.name = name + "老师"
        self.showInfo()
    }

初始化器的继承和重写

  • 不像在Object-C中的子类, Swift的子类不会默认继承父类的初始化器. Swift的这种机制防止父类的简单初始化器被一个更专用的子类继承并被用来创建一个没有完全或错误初始化的新实例的情况发生. 只有在特定情况下才会继承父类的初始化器

  • 如果你想自定义的子类来实现一个或多个和父类相同的初始化器, 你可以在子类中为那些初始化器提供定制的实现.

  • 当你写的子类初始化器匹配父类指定初始化器的时候, 你实际上可以重写那个初始化器. 因此, 在子类的初始化器定义之前你必须写override修饰符. 如同默认初始化器所描述的那样, 即使是自动提供的默认初始化器你也可以重写.

初始化器的自动继承

  • 如果你的子类没有定义任何指定初始化器, 他会自动继承父类所有的指定初始化器.

  • 如果你的子类提供了所有父类指定初始化器的实现--要么是通过规则1继承来的, 要么通过在定义中提供自定义实现的--那么它自动继承所有的父类便捷初始化器.

可失败初始化器

  • 定义类、结构体或枚举初始化时可以失败, 在某些情况下会管大用.这个失败可能由以下几种方式触发, 包括给初始化传入无效的形式参数值, 或缺少某种外部所需的资源, 又或是其他阻止初始化的情况.

  • 为了妥善处理这种可能失败的情况, 在类、结构体或枚举中定义一个或多个可失败的初始化器. 通过在init关键字后面添加问号init?来实现

必要初始化器

  • 在类的初始化器前添加required修饰符来表明所有该类的子类都必须实现该初始化器.

反初始化

  • 在类实例被释放的时候, 反初始化器就会立即被调用. 你可以使用deinit关键字来写反初始化器, 就如同写初始化器用init关键字一样. 反初始化器只在类类型中有效

  • 反初始化器会在实例被释放之前自动被调用. 你不能自定调用反初始化器. 父类的反初始化器可以被子类继承, 并且子类的反初始化器实现结束之后父类的反初始化器会被调用. 父类的反初始化器总会被调用, 就算子类没有反初始化器.

  • 每个类当中只能有一个反初始化器. 反初始化器不接收任何形式参数, 并且不需要写圆括号.

    deinit {

    }