Swift第六章 初始化、可选链(Optional Chaining)

初始化

初始化器

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

     // 指定初始化器
     init(parameters) {
         statements
     }
     // 便捷初始化器
     convenience init(parameters) {
         statements
     }
    
  3. 每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器
    1. 编译器会给每一个类默认一个初始化器init();但是一旦自定义,就不会默认添加 init()
  4. 默认初始化器总是类的指定初始化器
  5. 类偏向于少量指定初始化器,一个类通常只有一个指定初始化器
  6. 初始化器的相互调用规则
    1. 指定初始化器必须从它的直系父类调用指定初始化器
    2. 便捷初始化器必须相同的类里调用另一个初始化器
    3. 便捷初始化器最终必须调用一个指定初始化器
  7. 举例

     class Size {
         var width: Int
         var height: Int
            
         //指定初始化器(主要初始化器)
         init(width: Int,height:Int){
             self.width = width
             self.height = height
         }
         //便捷初始化器
         convenience init(width:Int){
             //便捷初始化器最终必须调用一个指定初始化器
             self.init(width: width, height: 0)
         }
         convenience init(height:Int){
             self.init(width: 0,height: height)
         }
            
         convenience init(){
             self.init(width:0,height:0)
         }
        
     }
        
        
     var s1= Size(width:10,height:20)
     var S2 = Size(width:10)
     var s3 = Size(height:20)
     var s4 = Size()
    
  8. 举例2

     class Person {
         var age: Int
         init(age: Int){
             self.age = age
         }
         convenience init(){
             self.init(age: 0)
         }
     }
        
     class Student :Person {
         var score: Int
         //指定初始化器必须从它的直系父类调用指定初始化器
         init(age: Int,score: Int){
             //先调用自己,再调用父类
             self.score=score
             //必须调用直系父类的指定初始化器
             super.init(age: age)
         }
            
         //便捷初始化器必须从相同的类里调用另一个初始化器(指定、便捷都行)
         convenience init(){
             //调用自己的便捷初始化器
             self.init(score: 0)
         }
            
         //便捷初始化器最终必须调用一个指定初始化器
         convenience init(score:Int){
             self.init(age:0,score: score)
         }
     }
    

初始化器的相互调用

1.png

  1. Designated 指定初始化器。convenience:便捷初始化器。
  2. 这一套规则保证了,使用任意初始化器,都可以完整地初始化实例
  3. 保证安全,让所有的初始化都必须走指定初始化器。
  4. 便捷初始化器只能横向调用,不能被子类调用
  5. 理念:使用者可以使用多种方式初始化实例,但是所有的初始化都要经过指定的初始化器。

两段式初始化

  1. Swift在编码安全方面是煞费苦心,为了保证初始化过程的安全,设定了两段式初始化、 安全检查
  2. 两段式初始化
    1. 第1阶段:初始化所有存储属性
      1. 外层调用指定\便捷初始化器
      2. 分配内存给实例,但未初始化
      3. 指定初始化器确保当前类定义的存储属性都初始化
      4. 指定初始化器调用父类的初始化器,不断向上调用,形成初始化器链
    2. 第2阶段:设置新的存储属性值
      1. 从顶部初始化器往下,链中的每一个指定初始化器都有机会进一步定制实例
      2. 初始化器现在能够使用self(访问、修改它的属性,调用它的实例方法等等),即self必须在第一阶段之后才能使用。

         init (age :Int) {
             //第一阶段初始化
             self.age = age
             super.init(age)
                        
             //self必须放在下面
             self.test();
         }
        
      3. 最终,链中任何便捷初始化器都有机会定制实例以及使用self

安全检查

  1. 指定初始化器必须保证在调用父类初始化器之前,其所在类定义的所有存储属性都要初始化完成
  2. 指定初始化器必须先调用父类初始化器,然后才能为继承的属性设置新值,即:必须先把父类的属性初始化完成,才能使用、修改父类的属性。
  3. 便捷初始化器必须先调用同类中的其它初始化器,然后再为任意属性设置新值

      convenience init(){
         //调用自己的其他初始化器
         self.init(score: 0)
         //再为任意属性设置新值
         self.test()
     }
    
  4. 初始化器在第1阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self
  5. 直到第1阶段结束,实例才算完全合法

重写初始化器

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

class Student :Person {
    var score: Int
    init(age: Int,score: Int){
        self.score = scoresuper.init(age: age)
    }
    //指定初始化器匹配父类便捷初始化器,不用添加override
    init(){
        self.score = 0
        self.init(age:0,score:0)
    }
    
    //便捷初始化器匹配父类便捷初始化器,不用添加override
    convenience init(){
        self.init(age:0,score:0)
    }
    
    //指定初始化器重写父类的指定初始化器,必须使用override
    override init(age: Int){
        //必须先自己初始化
        self.score =0
        super.init(age: age)
    }
    //便捷初始化器重写父类的自定初始化器,必须使用override
    override convenience init(age: Int){
        self.init(age:age,score:0)
    }
}

自动继承

  1. 如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器
  2. 如果子类提供了父类所有指定初始化器的实现(要么通过方式1继承,要么重写:可以重写成便捷,也可以是重写指定)
    1. 子类自动继承所有的父类便捷初始化器
  3. 就算子类添加了更多的便捷初始化器,这些规则仍然适用

     class Person {
         var age: Int
         var name: String
         init(age: Int,name: String){
             self.age = age
             self.name = name
         }
         init(){
             self.age = 0
             self.name =""
         }
         convenience init(age:Int){
             self.init(age: age,name:"")
         }
         convenience init(name:String){
             self.init(age:0,name:name)
         }
     }
        
     class Student :Person {
         init(no: Int){
             super.init()
         }
            
         //当前便捷重写父类的所有指定
         override convenience init(age: int,name: String){
             self.init(no:0)
         }
         //当前便捷重写父类的所有指定
         override convenience init(){
             self.init(no:0)
         }
     }
        
     //子类继承父类的所有便捷
     var s1 = Student(name:"xxx")
    

required

  1. 用required修饰指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写实现)
  2. 如果子类重写了required初始化器,也必须加上required,不用加override

     class Person {
         required init() { }
         init(age: Int) { }
     }
     class Student : Person {
         //1. 子类不定义任何指定初始化器,自动继承
         //2. 子类必须实现
         required init() {
             super.init()
         }
     }
    

属性观察器

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

     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
         }
     }
        
     // willSet 1
     // didSet 0 1
     var stu = Student()
    

可失败初始化器

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

     class Person {
         var name: String
         //初始化失败可以返回nil
         init?(name: String) {
             if name.isEmpty {
                 return nil
             }
             self.name = name
         }
     }
    
  2. 之前接触过的可失败初始化器

     var num = Int("123")
     //源码
     public init?(_ description: String)
        
     enum Answer : Int {
         case wrong, right
     }
     var an = Answer(rawValue: 1)
    
  3. 不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器和非可失败初始化器,即不能重载
  4. 可以用init!定义隐式解包的可失败初始化器
  5. 可失败初始化器可以调用非可失败初始化器,非可失败初始化器调用可失败初始化器需要进行解包
  6. 如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都失败,并且之后的代码都停止执行
  7. 可以用一个非可失败初始化器重写一个可失败初始化器,但反过来是不行的

     class Person {
         var name: String
         init?name: string){
             if name.isEmpty {
                 return nil
             }
         self.name = name
         }
         //非可失败,调用可失败
         convenience init(){
             //需要进行解包,如果nil解包程序会崩溃
             self.init(name: "")!
         }
     }
        
     //init!定义隐式解包的可失败初始化器
     class Person {
         var name: String
            
         init!name: string){
             if name.isEmpty {
                 return nil
             }
             self.name = name
         }
         //非可失败,调用可失败
         convenience init(){
             //不需要进行解包
             self.init(name: "")!
         }
     }
    

反初始化器(deinit)

  1. deinit叫做反初始化器,类似于C++的析构函数、OC中的dealloc方法
  2. 当类的实例对象被释放内存时,就会调用实例对象的deinit方法

     class Person {
         deinit {
             print("Person对象销毁了")
         }
     }
    
  3. deinit不接受任何参数,不能写小括号,不能自行调用
  4. 父类的deinit能被子类继承
  5. 子类的deinit实现执行完毕后会调用父类的deinit,子类不需要使用super主动调用,系统自动调用,

可选链(Optional Chaining)

  1. 举例

     class Car { var price = 0 }
     class Dog { var weight = 0 }
     class Person {
         var name: String = ""
         var dog: Dog = Dog()
         var car: Car? = Car()
         func age() -> Int { 18 }
         func eat() { print("Person eat") }
         subscript(index: Int) -> Int { index }
     }
     var person: Person? = Person()
     //若person为nil则会闪退
     var age1 = person!.age() // Int
     //编译器先检查person是否为nil,如果为nil就不再继续执行,不会闪退
     var age2 = person?.age() // Int?
     var name = person?.name // String?
     var index = person?[6] // Int?
        
     func getName() -> String { "jack" }
     // 如果person是nil,不会调用getName()
     person?.name = getName()
    
  2. 如果可选项为nil,调用方法、下标、属性失败,结果为nil
  3. 如果可选项不为nil,调用方法、下标、属性成功,结果会被包装成可选项
  4. 如果结果本来就是可选项,不会进行再次包装

     //可选绑定
     if let _ = person?.eat() { // ()?
         print("eat调用成功")
     } else {
         print("eat调用失败")
     }
    
  5. 多个?可以链接在一起
    1. 如果链中任何一个节点是nil,那么整个链就会调用失败
     var dog = person?.dog // Dog?
     var weight = person?.dog.weight // Int?
     var price = person?.car?.price // Int?
    
  6. 举例

     //字典中取值是可选类型,因为key值可以乱写
     var scores = ["Jack": [86, 82, 84], "Rose": [79, 94, 81]]
     //如果取值为nil,则直接为nil,反之则赋值
     scores["Jack"]?[0] = 100
     scores["Rose"]?[2] += 10
     scores["Kate"]?[0] = 88
        
     var num1: Int? = 5
     num1? = 10 // Optional(10)
     var num2: Int? = nil
     num2? = 10 // nil
        
     var dict: [String : (Int, Int) -> Int] = [
         //(+)编译器缩写:一个函数两个参数相加,并返回相加结果。
         "sum" : (+),
         "difference" : (-)
     ]
     //先判断字典取值是否为nil,然后再传递参数,获取结果。
     var result = dict["sum"]?(10, 20) // Optional(30), Int?
    
Table of Contents