12.初始化

83 阅读12分钟

初始化

初始化器

基本概念

  • 类、结构体、枚举都可以定义初始化器
  • 类有2种初始化器:指定初始化器(designated initializer)、便捷初始化器(convenience initializer)

语法格式

// 指定初始化器
init(parameters) {
    statements
}

// 便捷初始化器
convenience init(parameters) {
    statements
}

这里可以简单区分不加convenience就是指定初始化器

初始化器的规则

  • 每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器
  • 默认初始化器总是类的指定初始化器
  • 类偏向于少量指定初始化器,一个类通常只有一个指定初始化器

初始化器的相互调用规则

  1. 指定初始化器必须从它的直系父类调用指定初始化器
  2. 便捷初始化器必须从相同的类里调用另一个初始化器
  3. 便捷初始化器最终必须调用一个指定初始化器

初始化器的相互调用

调用关系图

image.png

调用规则总结:

  1. 子类的指定初始化器必须调用到父类的指定初始化器
  2. 子类的便捷初始化器可以调用子类自己的别的便捷初始化器,但是最终必须要调用子类自己的主要初始化器。然后自己的主要初始化器最终要求调用父类的指定初始化器
  3. 为啥要用这一套调用规则?最终目的是为了走指定初始化器,指定初始化为主要的初始化器,重要的逻辑都在里面,这一套逻辑是为了将主要的这些重要的初始化逻辑都走一遍。

示例代码

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)
    }
    
    // 便捷初始化器
    convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
    
    // 重写父类的便捷初始化器
    override convenience init() {
        self.init(name: "Unnamed Recipe")
    }
}

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    
    // 指定初始化器
    init(name: String, quantity: Int, purchased: Bool) {
        self.purchased = purchased
        super.init(name: name, quantity: quantity)
    }
    
    // 便捷初始化器
    convenience init(name: String, purchased: Bool) {
        self.init(name: name, quantity: 1, purchased: purchased)
    }
}

规则保证

这一套规则保证了:使用任意初始化器,都可以完整地初始化实例

两段式初始化

Swift在编码安全方面煞费苦心,为了保证初始化过程的安全,设定了两段式初始化安全检查。还是为了安全,安全是排在第一位。

个人对两段式初始化的理解:

  1. 在当前类里面进行初始化的时候,第一步先做的一定是初始化自身的属性(这里指的不是从父类继承过来的),这是第一步,完事后调用父类的主要初始化器。父类的只要初始化器里面干的第一件事也是初始化属性。直到基类初始化自身的属性,才可以在当前类里面使用self关键字进行调用操作。直到基类初始化自身的属性初始化第一个阶段才算执行完,为啥只有基类的熟悉初始化完才行呢?因为到基类都未初始化,意味self是不安全
  2. 前面初始化完后,可以往里面存储属性值。意味着可以改变存储属性值

里面的注意点:

  1. 必须在调用父类初始化前初始化自己所有的变量
  2. 不论属性的值是自身的还是父类的都要等第一个阶段执行完才能执行
  3. 直到第1阶段结束,实例才算完全合法。

第1阶段:初始化所有存储属性

  1. 外层调用指定/便捷初始化器
  2. 分配内存给实例,但未初始化
  3. 指定初始化器确保当前类定义的存储属性都初始化
  4. 指定初始化器调用父类的初始化器,不断向上调用,形成初始化器链

第2阶段:设置新的存储属性值

  1. 顶部初始化器往下,链中的每一个指定初始化器都有机会进一步定制实例
  2. 初始化器现在能够使用self(访问、修改它的属性,调用它的实例方法等等)
  3. 最终,链中任何便捷初始化器都有机会定制实例以及使用self

两段式初始化的示例

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
    
    init() {
        // 第1阶段:初始化所有存储属性
        numberOfWheels = 0
        // 第2阶段:可以使用self
        print("Vehicle initialized")
    }
}

class Bicycle: Vehicle {
    override init() {
        // 第1阶段:初始化当前类的存储属性(如果有)
        super.init()  // 调用父类初始化器
        // 第2阶段:可以修改继承来的属性
        numberOfWheels = 2
        print("Bicycle initialized")
    }
}

安全检查

Swift编译器执行四项安全检查,以确保两段式初始化能够顺利完成:

检查1:属性初始化

指定初始化器必须保证在调用父类初始化器之前,其所在类定义的所有存储属性都要初始化完成。

class Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        // 必须先初始化所有存储属性
        self.name = name
        self.age = age
        // 然后才能调用父类初始化器(如果有)
        super.init()
    }
}

检查2:继承属性设置

指定初始化器必须先调用父类初始化器,然后才能为继承的属性设置新值。

class Student: Person {
    var grade: String
    
    init(name: String, age: Int, grade: String) {
        // 1. 先初始化当前类的存储属性
        self.grade = grade
        // 2. 调用父类初始化器
        super.init(name: name, age: age)
        // 3. 然后才能修改继承的属性
        self.name = name + " (Student)"
    }
}

检查3:便捷初始化器调用

便捷初始化器必须先调用同类中的其它初始化器,然后再为任意属性设置新值。

class Rectangle {
    var width: Double
    var height: Double
    
    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }
    
    convenience init(side: Double) {
        // 必须先调用其他初始化器
        self.init(width: side, height: side)
        // 然后才能修改属性
        print("Square created with side: \(side)")
    }
}

检查4:第1阶段完成前的限制

初始化器在第1阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self

直到第1阶段结束,实例才算完全合法。


重写

重写指定初始化器

当重写父类的指定初始化器时,必须加上override(即使子类的实现是便捷初始化器)。

class Vehicle {
    var numberOfWheels = 0
    
    init() {
        numberOfWheels = 0
    }
}

class Car: Vehicle {
    var brand: String
    
    // 重写父类的指定初始化器
    override init() {
        brand = "Unknown"
        super.init()
        numberOfWheels = 4
    }
}

重写便捷初始化器

如果子类写了一个匹配父类便捷初始化器的初始化器,不用加上override

因为父类的便捷初始化器永远不会通过子类直接调用,因此,严格来说,子类无法重写父类的便捷初始化器。

个人总结:什么是重写?就是子类有可能通过 super关键字这样同一个方法名,同一个参数,这时候才存在重写的可能性,如果子类压根不能通过super调用到,就压根不存在重写,不存在重写当然也不需要加override.这里的情况明显是子类永远不会去调父类的便捷初始化器,不会存在这种情况,也不需要加override

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() {
        self.init(name: "Unnamed Recipe", quantity: 1)
    }
}

自动继承

在特定条件下,子类会自动继承父类的初始化器: 用自己的话总结几条继承规则: 1.如果子类没有自己的指定初始化器,会自动从父类那里继承 2.如果你有了父类所有的指定初始化器,那么父类的便捷初始化器你也有了。为啥这里这样规定,因为便捷初始化器依赖于指定初始化器,只要你指定的初始化器有了,不管你是继承来的,还是你自己的,那么便捷初始化器都可以用了,这里的逻辑便捷初始化器你都有了。还有一点就是你有自己的便捷初始化器,不影响前面说的继承关系和逻辑。

规则①:继承所有指定初始化器

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

class Food {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "Unnamed")
    }
}

class SimpleFood: Food {
    // 没有自定义指定初始化器
    // 自动继承父类的 init(name:) 指定初始化器
}

let food = SimpleFood(name: "Apple")  // 可以调用继承的初始化器

规则②:继承所有便捷初始化器

如果子类提供了父类所有指定初始化器的实现(要么通过方式①继承,要么重写),子类自动继承所有的父类便捷初始化器。

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)
    }
    
    // 自动继承父类的便捷初始化器 convenience init()
}

let ingredient = RecipeIngredient()  // 可以调用继承的便捷初始化器

规则③:添加便捷初始化器不影响继承

就算子类添加了更多的便捷初始化器,这些规则仍然适用。

规则④:便捷初始化器重写也满足规则②

子类以便捷初始化器的形式重写父类的指定初始化器,也可以作为满足规则②的一部分。


required

required的作用

required修饰指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写实现)。 required 的意思是被要求,被要求什么呢?要求谁呢? 是要求子类必须实现这个便捷初始化器。

使用required

class Person {
    required init() {
        // 必须实现的初始化器
    }
    
    init(age: Int) {
        // 普通初始化器
    }
}

class Student: Person {
    required init() {
        super.init()
    }
    
    // 不需要实现init(age:),因为它不是required的
}

required的特点

  • 如果子类重写了required初始化器,也必须加上required,不用加override 这里个人理解是如果子类已经加了required,必然是重写了父类这个方法的意思,也就是说required已经包含了重写的意思了,所以不需要啰里啰嗦的加上override
  • 通过自动继承满足required初始化器的要求时,不需要显式实现

属性观察器

初始化器中的属性观察器

父类的属性在它自己的初始化器中赋值不会触发属性观察器,但在子类的初始化器中赋值会触发属性观察器。

class Person {
    var age: Int {
        willSet {
            print("willSet", newValue)
        }
        didSet {
            print("didSet", oldValue, age)
        }
    }
    
    init() {
        // 在父类初始化器中赋值不会触发观察器
        self.age = 0
    }
}

class Student: Person {
    override init() {
        super.init()
        // 在子类初始化器中赋值会触发观察器
        self.age = 1
    }
}

var stu = Student()
// willSet 1
// didSet 0 1

为什么会这样?

因为在第1阶段,属性观察器不会被调用,在第2阶段,属性观察器会被调用,父类的初始化器在第1阶段执行,子类修改继承属性在第2阶段执行

可失败初始化器

基本概念

类、结构体、枚举都可以使用init?定义可失败初始化器。

class Person {
    var name: String
    
    init?(name: String) {
        if name.isEmpty {
            return nil
        }
        self.name = name
    }
}

let person = Person(name: "")  // nil
let validPerson = Person(name: "John")  // Person实例

常见的可失败初始化器

// 字符串转整数
var num = Int("123")  // Optional(123)
public init?(_ description: String)

// 枚举的原始值初始化
enum Answer: Int {
    case wrong, right
}
var an = Answer(rawValue: 1)  // Optional(Answer.right)

可失败初始化器的规则

  1. 不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器和非可失败初始化器。因为你这样搞,编译器搞不清楚你调用的是哪个了。
  2. 可以用init!定义隐式解包的可失败初始化器,就是说他帮你解包了
  3. 可失败初始化器可以调用非可失败初始化器,非可失败初始化器调用可失败初始化器需要进行解包。这里也比较好理解,以为可失败的可以在没有可以初始化的条件下初始化失败,而在有条件的情况下,调用不可失败的进行正常的初始化工作
  4. 如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都失败,并且之后的代码都停止执行
  5. 可以用一个非可失败初始化器重写一个可失败初始化器,但反过来是不行的

可失败初始化器示例

struct Animal {
    let species: String
    
    init?(species: String) {
        if species.isEmpty {
            return nil
        }
        self.species = species
    }
}

class Pet: Animal {
    let name: String
    
    init?(species: String, name: String) {
        if name.isEmpty {
            return nil
        }
        self.name = name
        super.init(species: species)  // 调用父类可失败初始化器
    }
    
    // 重写为非可失败初始化器
    override init(species: String) {
        self.name = "Unknown"
        super.init(species: species)!  // 强制解包
    }
}

反初始化器

基本概念

deinit叫做反初始化器,类似于C++的析构函数、OC中的dealloc方法。

当类的实例对象被释放内存时,就会调用实例对象的deinit方法。

基本语法

class Person {
    var name: String
    
    init(name: String) {
        self.name = name
        print("Person \(name) initialized")
    }
    
    deinit {
        print("Person \(name) deinitialized")
    }
}

var person: Person? = Person(name: "John")
person = nil
// Person John initialized
// Person John deinitialized

反初始化器的特点

  • deinit不接受任何参数,不能写小括号,不能自行调用
  • 父类的deinit能被子类继承
  • 子类的deinit实现执行完毕后会调用父类的deinit

继承中的反初始化器

class Animal {
    var species: String
    
    init(species: String) {
        self.species = species
        print("Animal \(species) initialized")
    }
    
    deinit {
        print("Animal \(species) deinitialized")
    }
}

class Dog: Animal {
    var name: String
    
    init(name: String, species: String) {
        self.name = name
        super.init(species: species)
        print("Dog \(name) initialized")
    }
    
    deinit {
        print("Dog \(name) deinitialized")
        // 自动调用父类的deinit
    }
}

var dog: Dog? = Dog(name: "Buddy", species: "Golden Retriever")
dog = nil
// Animal Golden Retriever initialized
// Dog Buddy initialized
// Dog Buddy deinitialized
// Animal Golden Retriever deinitialized