Swift5.1-初始化

191 阅读7分钟

初始化器

类、枚举、结构体都可以定义初始化器。

类有两种初始化器: 指定初始化器(designated initializer)、便捷初始化器(convenience initializer).

class Person {
    var age: Int
    
    // 指定初始化器
    init(age: Int) {
        self.age = age
    }
    
    // 便捷初始化器
    convenience init() {
        self.init(age: 10)
    }
}

知识点:

  • 每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器。

  • 默认初始化器总是类的指定初始化器。

  • 类偏向于少量的指定初始化器,一个类通常只有一个指定初始化器。

  • 初始化器的相互调用规则

    1. 子类指定初始化器必须从它的直系父类调用指定初始化器。
    2. 便捷初始化器必须从相同类里调用另一个初始化器。
    3. 便捷初始化器最终必须调用一个指定初始化器。
class Person {
    var age: Int
    var name: String = ""
    
    // 指定初始化器(主线1)
    init(age: Int) {
        self.age = age
    }
    
    // 指定初始化器 (主线2)
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    
    // 便捷初始化器
    convenience init() {
        self.init(name: "long")  // 2. 便捷初始化器必须从相同类里调用另一个初始化器。
    }
    
    convenience init(name: String) {
        self.init(age: 10, name: name) // 3. 便捷初始化器最终必须调用一个指定初始化器。
    }
}

class Student: Person {
    init() {
        super.init(age: 10)  // 1. 子类的指定初始化器必须从它的直系父类调用指定初始化器。
    }
}
let stu = Student()

初始化器的相互调用: avatar 如图可知:

  1. 指定初始化器的调用是纵向的。(子类调父类)
  2. 便捷初始化器的调用时横向的。(同类内调用)

这套规则保证了,任意初始化器都可以完整初始化实例。

两段式初始化

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

两段式初始化

  • 第一阶段:初始化所有的存储属性。 1. 外层调用指定\便捷初始化器。 2. 分配内存给实例,但未初始化。 3. 指定初始化器确保当前类定义的存储属性都被初始化。(自己的存储属性先初始化,然后再初始化父类的) 4. 子类指定初始化器调用父类的指定初始化器,不断地往上调用,形成初始化链。

  • 第二阶段:设置新的存储属性的值。 1. 从顶部初始化器往下,链中的每一个指定初始化器都有机会进一步定制实例。 2. 初始化器现在能够使用self(访问、修改它的属性、调用它的实例方法。等等) 3. 最终,链中任何便捷初始化器都有机会定义实例以及使用self.

 cl	ass Person {
    var age: Int
    var name: String

    // 指定初始化器
    init(age: Int, name: String) {
        // 第一阶段,初始化所有存储属性
        self.age = age
        self.name = name
        
        // 第二阶段,个性化定制,可以使用self(访问、修改存储属性的值,调用方法)
        self.run()
    }
    
    convenience init(name: String) {
        // 第一阶段,初始化所有存储属性
        self.init(age: 10, name: name)
        
        // 第二阶段,个性化定制,可以使用self(访问、修改存储属性的值,调用方法)
        self.run()
    }
    
    func run() {
        print("Person Runn")
    }
}
// Student 类
class Student: Person {
    var weight: Int
    init() {
        // 第一阶段,初始化所有存储属性
        self.weight = 20 // 先确保自己的存储属性先初始化
        super.init(age: 10, name: "long")  // 调用父类的指定初始化器,初始化父类的存储属性
        
        // 第二阶段,个性化定制,可以使用self(访问、修改存储属性的值,调用方法)
        self.age = 15
        self.run()
    }
    
    func playing() {
        print("Student playing")
    }
}

安全检查

  • 指定初始化器必须保证在调用父类的指定初始化器之前,其所在类的所有存储属性都要初始化完成。
  • 指定初始化器必须先调用父类的指定初始化器,然后才能为继承的属性赋值。
  • 便捷初始化器必须先调用同类中的其他初始化器,然后再为其任意属性赋值。
  • 初始化器在完成第一阶段之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self.
  • 直到第一阶段结束,实例才算完全合法。

官网图: avatar

重写

  • 当重写父类的指定初始化器时,必须加上override(即使子类的实现是便捷初始化器)
  • 如果子类写了一个匹配父类便捷初始化器的初始化器,不用加上override, 因为父类的便捷初始化器永远不会通过子类来直接调用(便捷初始化器只能横向调用),因此,严格来说,子类无法重写父类的便捷初始化器的。
class Person {
    var age: Int
    init(age: Int) {
        self.age = age
    }
    convenience init() {
        self.init(age: 10)
    }
}

class Student: Person {
    // 当重写父类的初始化器时,必须加上`override`
    override init(age: Int) {
        super.init(age: age)
    }
    // 如果子类写了一个匹配父类便捷初始化器的初始化器,不用加上`override`
    convenience init() {
        self.init(age: 10)
    }
}

自动继承

  1. 如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器。
  2. 如果子类提供了父类所有指定初始化器的实现(要么通过继承、要么是重写),那么子类会自动继承父类所有的便捷初始化器。
  3. 就算子类添加了再多的便捷初始化器,这些规则依然适用。
  4. 子类以便捷初始化器的形式重写父类指定初始化器,也可以作为满足规则2的一部分。

required

  1. required 修饰指定初始化器,表明其所有子类都必须实现该初始化器。(继承或者重写)
  2. 如果子类重写了required初始化器,也必须加上required,不用加override
class Person {
    var age: Int
    required init(age: Int) {
        self.age = age
    }
    convenience init() {
        self.init(age: 10)
    }
}

class Student: Person {
    required init(age: Int) {   // 父类是required,子类也必须使用required修饰,保证其子类也必须实现
        super.init(age: 10)
    }
}

属性观察器

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

class Person {
    var age: Int {
        willSet {
            print("Person willSet:", newValue)
        }
        
        didSet {
            print("Person didSet:", oldValue, age)
        }
    }
    
    init() {
        self.age = 10   // 不会触发属性观察器
    }
    
}

class Student: Person {
    override init() {
        super.init()
        self.age = 20  // 会触发属性观察器
    }
}

// 输出:
Person willSet: 20
Person didSet: 10 20

可失败初始化器

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

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

let person = Person(name: "")   // 返回为nil
let person2 = Person(name: "long") // 返回 Option(Person) 可选项

注意:

  1. 不允许同时定义参数个数、参数标签、参数类型相同的可失败初始化器和非可失败初始化器。
  2. 可以用init!定义隐式解包的可失败初始化器。
  3. 可失败初始化器可以调用非可失败初始化器,非可失败初始化器调用可失败初始化器时需要进行解包。
  4. 如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都失败,并且之后的代码都停止运行。
  5. 可以用一个非可失败初始化器来重写一个可失败初始化器,但反过来是不行的。
class Person {
    var name: String
    init!(name: String) {   // init! 隐式解包
        if name.isEmpty {
            return nil
        }
        self.name = name
    }
    convenience init() {
        self.init(name: "")  // 指定初始化器使用了隐式解包,所以这个不用在后面加!, 但是使用隐式解包是有风险的,如本例子,如果外部使用了便捷初始化器初始化,则会发生崩溃。
    }
}

反初始化器(deinit)

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

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

class Person {
    deinit {
        print("Person deinit")
    }
}

var person: Any = Person()
person = 10  // person 重新赋值别的值后,上面的Person()对象就会被释放,会调用deinit方法。
  • deinit 方法不接受任何参数、不能写小括号、不能自行调用
  • 父类的deinit能被子类继承
  • 子类的deinit实现执行完毕后会调用父类的deinit。(和初始化过程一样,先确保子类的先完成,在调用父类的)
class Person {
    deinit {
        print("Person deinit")
    }
}
class Student: Person {
    deinit {
        print("Student deinit")
    }
}
var stu: Any = Student()
stu = 10

// 输出:
Student deinit
Person deinit

参考:

MJ Swift入门到精通 课程

Apple Swift文档