Swift中的多态、初始化、可选链

重写override

  • 重写类型方法、下标

    1. class修饰的类型方法、下标,允许被子类重写
    2. static修饰的类型方法、下标,不允许被子类重写

    注意:如果此时继承父类重写父类方法之后,如果重写的方法不想继续被子类去重写,那么可以此时在重写的方法前面添加static关键字修饰来确保子类不能够重写当前的方法

    class AnimalSubscript {
        func speak() {
            print("animal speak")
        }
        subscript(index: Int) -> Int {index}
        
        class func eat() {
            print("animal eat")
        }
        
        static func drink() {
            print("animal drink")
        }
    }
    
    class Cat: AnimalSubscript {
        override func speak() {
            print("cat speak")
        }
        
        override subscript(index: Int) -> Int {index + 20}
        override class func eat() {
            print("cat eat")
        }
        
        //此时重写的方法将不能被子类重写修改
//        override static func eat(){
//            print("终结当前的方法")
//        }
        
    }
  • 重写属性
    1. 子类可以将父类的计算属性、存储属性重写为计算属性
    2. 子类不可以将父类重写为存储属性
    3. 只能重写var变量属性。不能重写let属性
    4. 重写时,属性名、类型要一致
    5. 子类重写后的属性权限不能小于父类属性的权限
    6. 如果父类属性是只读的,那么子类重写后的属性可以是只读的,也可以是可读写
    7. 如果父类属性是可读可写的,那么子类重写后的属性也必须是可读写的
    class OverrideCircle {
        //懒加载
        lazy var lazyVar: Int = {
            return 1
        }()
        
        private(set) var onlyRead: Int = 0
        var radius: Int = 0
        var diameter: Int{
            get {
                print("get diameter")
                return (radius * 2)
            }
            
            set {
                radius = newValue
                print("Circle set diameter")
            }
        }
        
    }
    
    class OverrideCircleSon: OverrideCircle {
        
        var newRadius = 0
        override var diameter: Int {
            get {
                print("overrideCircleSon get value")
                return radius * 2
            }
            
            set {
                print("overrideCircleSon set value")
                radius = newValue
            }
        }
        
        override var radius: Int{
            get {
                print("reWrite stored perproty of radius")
                return newRadius
            }
            
            set {
                print("reGet sotre perproty of radius")
                newRadius = newValue
            }
        }
    }
  • 属性观察器
    1. 可以在子类为父类属性(除了只读计算属性、let属性)增加属性观察器
    2. set 与willsetget 与 willget 均不能共存的
    3. 不管父类是实例属性,类型属性或者存储属性,计算属性,子类都可以为父类添加观察属性
    class perprotyObserverClass: OverrideCircle{
        override var radius: Int{
            willSet {
                print("will set value", newValue)
            }
            didSet {
                //此时的radius不会引发死循环,因为此时并没有重写计算属性,此时访问的仍旧是radius本身,
                //属性监听器的实现是通过中间临时变量来实现的,所以不存在死循环
                print("set value finish", oldValue, radius)
            }
        }
        //不能够正常添加观察期监听
//        override var onlyRead: Int{
//            willSet {
//
//            }
//        }
        override var lazyVar: Int{
            willSet {
                print("lazy load will set")
            }
        }
    }
  • final 使用
    1. final修饰的方法、下标、属性,禁止被重写
    2. final修饰的类,禁止被继承
final class finalClass {
        final var radius: Int {
            set {
                print("radius write")
            }
            get {
                print("circle get radius")
                return 20
            }
        }
        
    }

多态

  1. 多态是指:父类指针指向子类对象的行为现象
  2. 在OC中多态是依靠runtime来实现的,而Swift中的实现方式类似于C++中的虚函数表来实现的

几个问题:

  1. 父类是如何判断对应的子类对象的类型的(多态)
  2. 如果将class类型换成struct类型,那么在内存中调用的时候有什么区别?
    因为对于结构体来说,存储在栈空间,所以存储的地址在编译期间就已经确定。而对象的实例,需要堆空间去动态分配内存,此时的堆空间的地址是动态分配的,由此调用对应的方法由于对象实例分配动态的原因,并不能直接在编译期间直接找到其内存地址,所以需要在运行的时候去找到动态分配的地址空间,进而去通过该地址间接找到要调用的方法地址,因此对象的方法的调用地址是变化的。所以相比于类调用方法,结构体调用的方法执行效率要比放在类中执行的效率高很多

初始化器

  • 特点

    1. 类、结构体、枚举都可以定义初始化器
    2. 类有两种初始化器:指定初始化器(designed initializer)、便捷初始化器(convenience initializer)
    3. 每个类至少有一个初始化器,指定初始化器是类的主要初始化器
    4. 默认初始化器总是类的指定初始化器
    5. 类偏向于少量的指定初始化器,一个类通常只有一个指定初始化器
    6. 初始化过程(为了保证初始化安全,设定了两段式初始化安全检查)
  • 初始化器的相互调用规则

    1. 指定初始化器必须从它的直系父类调用指定初始化器
    2. 便携初始化器必须从相同的类里调用另一个初始化器
    3. 便携初始化器最终必须调用一个指定初始化器
    class Person {
        var name: String
        init(name: String) {
            self.name = name
        }
    }
    
    class Student: Person {
        
        var score: Int
        var height: Double
        
        //指定初始化器
        init(score: Int, height: Double) {
            self.score = score
            self.height = height
            super.init(name: "student")
        }
        
        //便携初始化器
        convenience init(score: Int) {
            self.init(score:99, height: 20.0)
            self.score = score
        }
        
        //重写父类的指定初始化器为便捷初始化器
        convenience override init(name: String) {
            self.init(score: 98, height: 177)
        }
        
    }
  • 两段式初始化过程

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

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

    • 如果存在继承关系,初始化顺序如下:

      1. 先初始化指定初始化器完成子类的初始化
      2. 依次调用父类的指定初始化器完成初始化操作
    • 安全检查
      1. 指定初始化器必须保证在调用父类初始化之前,其所在类定义的所有存储属性都要初始化完成
      2. 指定初始化器必须先调用父类初始化器,然后才能为继承的属性设置新值
      3. 便捷初始化器必须先调用同类中的其他初始化器,然后再为任意属性设置新值
      4. 初始化器在第一阶段初始化完成之前,不能调用任何实例方法,不能读取任何实例属性的值,也不能引用self
      5. 直到第一阶段完成,实例才能算完全合法

    • 重写父类的指定初始化器
      1. 必须加上override(即使子类实现的是便捷初始化器)
      2. 因为父类的便捷初始化器永远不会通过子类直接调用,所以,子类无法重写父类的便捷初始化器

  • 自动继承

    • 特点
    1. 如果子类没有定义任何初始化器,那么它将自动继承父类所有的指定初始化器
    2. 如果子类提供了父类所有指定初始化器的实现(一种是全部重写、另一种是通过步骤1来实现),此时子类将会自动继承所有的父类的便捷初始化器
    3. 就算子类添加很多便捷初始化器,以上特点依旧适用
    4. 子类用便捷初始化器的形式重写父类的指定初始化器,此时步骤2也适用于这种情况
  • required使用

    • 特点
    1. required修饰指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写来实现)
    2. 如果子类重写了required初始化器,也必须加上required,不用加override
  • 初始化引发的属性观察器的变化
    特点:
    1. 父类的属性在它自己的初始化器中赋值不会触发属性观察器
    2. 在子类的初始化器中赋值会触发属性观察器

class Animal {
        var type: String {
            didSet {
                print("oldValue:\(oldValue), new value:\(type)")
            }
            willSet {
                print("newValue:\(newValue)")
            }
        }
        
        required init(type: String) {
            self.type = type
        }
    }
    
    class Dog: Animal {
        
        var age: Int
        init(age: Int) {
            self.age = age
            super.init(type: "dog")
        }
        
        required init(type: String) {
            self.age = 0
            super.init(type: "dog")
            self.type = "pig"
        }
        
    }
  • 反初始化器 deinit

    1. 类似于C++的析构函数、OC中的dealloc方法
    2. 当类的实例对象被释放内存的时候,会调用实例对象的deinit方法
    3. deinit不能接受任何参数、不能写小括号、不能自行调用
    4. 父类的deinit能被子类继承
    5. 子类的deinit实现执行完毕后,会调用父类的deinit
  • 可失败初始化器
    特点:

    1. 类、结构体、枚举都可以使用init?定义可失败初始化器
    2. 不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器非可失败初始化器
    3. 可失败初始化器可以调用非可失败初始化器非可失败初始化器调用可失败初始化器需要进行解包操作
    4. 如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都会失败,并且之后的代码都会停止
    5. 可以用一个非可失败初始化器重写一个可失败初始化器,但反过来是不行的
    6. 可以通过可选链来间接的进行判空容错(如果为nil,不执行后序语句内容)
class PersonCanFailed {
        var name: String
        var age: Int?
        init?(name: String) {
            if name.isEmpty {
                return nil
            }
            self.name = name
        }
        
        //定义一个便捷初始化器
        convenience init?() {
            self.init(name: "leo") //如果初始化失败,后序代码将不会再执行
            self.age = 10
        }
        
        //已经接触到的可失败初始化器
        func demo() {
            //可能初始化失败
            let _ = Int("12345555")
            enum Answer: Int{
                case wrong, right
            }
            //可能初始化失败
            let _ = Answer(rawValue: 1)
        }
    }

    func testOptinolChainUse() {
        var scores = ["Jack": [89, 99, 67],
                      "Rose": [89, 99, 67]]
        
        scores["Jack"]?[0] = 100
        scores["Rose"]?[2] = 87
        
        //判断下面num1与num2 内容是什么类型
        var num1: Int? = 5
        num1? = 10   //Optinoal(10)
        
        //num2? 等同于判定num2是否为空,如果为空后序赋值操作将取消,如果不为空则正常操作
        var num2: Int? = nil
        num2? = 10  //nil
        num2 = 19 //19
    }
    
    func testOptionalChainDictUse() {
        var dict:[String: (Int, Int) -> Int] = ["sum":(+), "difference":(-)]
        var result = dict["sum"]?(10, 20) //Optional(30), Int?
        
    }

可选链 ?

  • 可选链调用特点

    1. ?的作用是判断调用者是否有值,有的话会继续调用后序方法,没有的话,直接返回,返回的内容是可选类型(这个行为改变了原有的返回数据类型,将之前返回的内容包装成可选类型了)
    2. 方法没有返回值却能够使用var进行接收,原因是方法的调用默认返回值是Void,可以认定是一个空元组类型
    3. 可以通过可选链接收返回值的方法来判定方法是否调用成功
  • 其他需要注意的点

    1. 如果可选为nil,调用方法、下标、属性失效,结果为nil
    2. 如果可选项不为nil调用方法、下标、属性成功,结果会被包装成可选类型
    3. 如果结果本来就是可选项,不会进行再包装
    4. 多个?可选可以链接在一起
    5. 如果可选链中任何一个节点是nil,那么整个可选链就会调用失败
func testOptionlUse() {
        var person: CarPerson? = CarPerson()
        var age = person?.age()  //可选链调用
        var age1 = person!.age() //强制解包
        var name = person?.name  //可选链调用
        var result = person?.eat() //没有返回值仍能够通过变量去接受返回值
        
        if let _ = person?.eat() {
            print("调用eat成功")
        }else{
            print("调用eat失败")
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章