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失敗")
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章