天天看點

Swift-可空鍊式調用(Optional Chaining)(十五)

前言

其實可空鍊式調用并沒有它的名字那麼陌生,簡而言之就是對于可選類型

Optional

(使用問号 ? 字尾表示)和強制展開類型(使用感歎号 ! 字尾表示)的使用方法。在平常寫代碼的時候隻是大概的清楚哪些值是可空的,哪些值是肯定存在的,但是并沒有深究可空的調用有何優點,有何使用時需要注意的事項。至少前面寫不少示例代碼的時候,我也是大都按照自己的想法去定義的。這一小節就是對可空調用的較長的描述,至于鍊式,就是多層調用,大概就是

school.classroom.student.name

這樣的調用方法。

小節知識點,看着挺多挺長,但是如果把

可空鍊式調用

這幾個忽略掉,就會發現都是我們已經非常熟悉的基本用法:

  • 使用可空鍊式調用來強制展開
  • 使用可空鍊式調用定義模型類
  • 通過可空鍊式調用通路屬性
  • 通過可空鍊式調用來調用方法
  • 通過可控調用方法來通路下标
  • 多層連結
  • 對傳回可空值得函數進行連結

可空鍊式調用是一種可以請求和調用屬性、方法以及下标的過程,它的可空性展現在請求和調用的目前目前可能為空

nil

。如果可空的目标有值,那麼就會調用成功;如果選擇的目标為空,那麼這種調用将會傳回空

nil

。多個連續的調用可以被連結到一起形成一個調用鍊,如果其中任何一個節點為空

nil

,将會導緻整個調用失敗。

Swift的可空鍊式調用和Objective-C中的消息為空有點像,但是Swift可以使用在任何類型中,并且能夠檢查調用是否成功。一個可空調用相當于Objective-C中

if (message == nil) { } else { }

,隻是Objective-C中隻能用在對象或者變量中,遠沒有Swift中功能強大。

分條詳述

  1. 使用可空鍊式調用來強制展開

    下面這幾句話讀起來有點拗口,多了解就好:通過在想調用非空的屬性、方法、或下标的可空值

    optional value

    後面放置一個問号

    ?

    ,可以定義一個可空鍊。這一點很像在一個可空值後面放一個感歎号

    !

    來強制展開其中值。它們的主要差別在于當可空值為空時可空鍊式隻是調用失敗,然而強制揭開将會出發運作錯誤。

    簡單來說,就是當調用一個屬性或方法時,我們希望它是非空的,是有值的,但是這個我們并不能确定有值,就會用到可空鍊。譬如擷取網絡資料,然後展示,我們當然希望是擷取到資料的,但是有時候因為網絡、背景的問題,資料為空,此時如果用可空鍊來處理,就會簡單些。

    需要強調的是,可空鍊式調用的傳回結果與原本的傳回結果具有相同的類型,但是被包裝成了一個可空類型值。例如,當可空鍊式調用成功時,一個

    int

    類型的結果将會傳回

    int?

    類型。

    下面幾行示例代碼展示可空鍊式調用和強制展開的一些不同點:

    //  戶主資訊
    class Person {
    var residence: Residence?
    }
    //  房子資訊,一共有多少房間
    class Residence {
    var numberOfRooms =      //  預設隻有一個房間
    }
    //  建立一個新的 Person 執行個體
    let personOne = Person()
    //  由于 residence 屬性為空,是以下面如果将 ? 換成 ! 強制解開,會報錯。另外,這裡代碼補全就是帶 ? 的。
    print(personOne.residence?.numberOfRooms)     //  輸出: nil
    //  初始化 residence 屬性
    personOne.residence = Residence()
    //  此時 residence 已經有了初始值,但是膽碼補全的時候還是用了 ? ,此時如果将 ? 改為 ! , 并不會報錯,因為是對一個已經确定的非空值強制展開
    print(personOne.residence!.numberOfRooms)      //  輸出: 1
    print(personOne.residence?.numberOfRooms)      //  輸出: Optional(1)  ,  表示可空的
               
    其實可空鍊式調用最常用的一個場景就是

    if

    語句的判斷,當可空鍊式調用傳回是

    nil

    時執行操作A,不是

    nil

    時執行操作B,如下:
    //  可空鍊式調用用于 if 語句判斷
    if let person = personOne.residence?.numberOfRooms {
    //  此時不為空  personOne.residence?.numberOfRooms = 1
    } else {
    //  此時為空   personOne.residence?.numberOfRooms = nil
    }
               
  2. 通過可空鍊式調用定義模型類

    通過使用可空鍊式調用可以調用多層屬性、方法和下标。這樣可以通過各種模型向下通路各種子屬性。并且可以根據是否為空判斷是否可以通路子屬性的屬性、方法或下标。

    下面示例代碼定義了四個模型類,這些例子包括多層可空鍊式調用。同時,下面幾個知識點也會以這4個類為基礎說明:

    //  戶主資訊
    class Person {
    //  房子資訊,可能沒有房子,是以可選比較合适
    var residence: Residence?
    }
    //  房子資訊
    class Residence {
    //  房間數組
    var rooms = [Room]()
    //  一共有多少房間
    var numberOfRooms: Int {
        return rooms.count
    }
    //  房子位址
    var address: Address?
    //  下标方法,選擇第幾間房
    subscript(index: Int) -> Room {
        get {
            return rooms[index]     //  取值
        }
        set {
            rooms[index] = newValue    //  設定值
        }
    }
    //  列印房間數量
    func printNumberOfRooms() {
        print("number of rooms : \(rooms.count)")
    }
    }
    //  房間資訊
    class Room {
    //  房間名字
    let name: String
    init(roomName name: String) {
        self.name = name
    }
    }
    //  位址資訊
    class Address {
    var buildingName: String?  //  建築名字
    var buildingNumber: String?     //  建築編号
    var buildingStreet: String?      //  建築所在街道名稱
    //  方法,擷取建築物的唯一标示符,因為可能為空,是以傳回的String類型是可選的
    func buildingIdentifier() -> String? {
        if let name = buildingName {
            return name
        } else if let number = buildingNumber {
            return number
        } else {
            return nil
        }
    }
    }
               
  3. 通過可空鍊式調用通路屬性

    就是我們平常調用屬性、方法的那一套,先建立一個執行個體,然後去調用。其實,很多時候執行個體、屬性等是可空的還是強制展開的,系統都會幫我們去辨識的,我們需要去了解為什麼會出現補全的情況,有時候需要我們手動去決定可選還是強制展開。

    下面是代碼示例,主要注意點都寫在注釋裡了:

    //  建立一個戶主執行個體
    let john = Person()
    if let roomCount = john.residence?.numberOfRooms {
    //  可空鍊式調用成功,這裡不會執行,因為 john.residence?.numberOfRooms 此時是 nil
    } else {
    //  可空鍊式調用失敗,會執行這裡的代碼
    }
    //  通過可空鍊式調用設定屬性值
    john.residence = Residence()                //  首先執行個體化屬性  john.residence
    john.residence?.address = Address()         //  執行個體化位址
    //  此時,address 已經存在,才能修改它的屬性值
    john.residence?.address?.buildingName = "北京"
    print(john.residence?.address?.buildingIdentifier())     //  輸出: Optional("北京")
    print(john.residence!.address!.buildingIdentifier()!)    //  輸出:北京
    /*************** 上面的代碼必須每一個屬性都是強制展開的,任何一個可空時,都會輸出  Optional("北京") ***************/
               
  4. 通過可空鍊式調用來調用方法

    可以通過可空鍊式調用來調用方法,并判斷是否調用成功。即使這個方法沒有傳回值。其實沒有傳回值的方法隐式的傳回

    Void

    類型。這意味着沒有傳回值的方法也會傳回

    ()

    或者空的元組。

    如果在可空值上通過可空鍊式調用來調用沒有傳回值的方法,那麼傳回的類型将會是可選空類型

    Void?

    ,而不是

    Void

    。因為空過可空鍊式調用得到的傳回值都是可空

    nil

    的。

    下面幾行示例代碼結合上面的定義的資料類型,展示出可空傳回值可傳回空元組的方法的差別:

    //  不在可空鍊式調用上調用沒有傳回值的方法,預設是 Void 類型的傳回,傳回一個空的元組
    let residence = Residence()
    print(residence.printNumberOfRooms())      //  輸出:number of rooms : 0     ()
    //  在可空鍊式調用上調用沒有傳回值的方法,傳回的是一個可選的空類型 Void? ,因為隻有可選類型才能為 nil
    let tom = Person()
    print(tom.residence?.printNumberOfRooms())     //  輸出: nil
               
    同樣的,可以判斷通過可空鍊式調用來給屬性指派是否成功。首先,需要知道的是無法給一個

    nil

    的屬性指派:
    let jack = Person()
    let jackResidence = Residence()
    jack.residence? = jackResidence      //  這句話并沒有給 jack.residence? 指派
    //  此時 jack.residence? == nil
    if (jack.residence? = jackResidence) != nil{
    print("指派成功")
    } else {
    print("指派失敗")
    }
    //  輸出: 指派失敗
               
    作為對比,對比下面這段代碼和上面的差別:
    let jack = Person()
    let jackResidence = Residence()
    jack.residence = jackResidence     //  這句話給 jack.residence 了記憶體位址
    //  此時 jack.residence 已經被執行個體了
    if (jack.residence? = jackResidence) != nil{
    print("指派成功")
    } else {
    print("指派失敗")
    }
    //  輸出: 指派成功
               
  5. 通過可空鍊式調用來通路下标

    通過可空鍊式調用,可以用下标來對空值進行讀取或寫入,并且判斷下标是否調用成功。注意:當通過可空鍊式調用通路可空值得下标的時候,應該将問号放在下标方括号的前面而不是後面。可空鍊式調用的問号一般直接跟在可控表達式的後面。問号跟在哪個表達式的後面表示哪個表達式是可控的。

    其實通路下标和通路屬性、方法并沒有什麼不同,可用于

    if

    語句判斷,不能給一個值為

    nil

    的屬性指派:
    //  通路下标
    let rose = Person()
    if let firstRoomName = rose.residence?.rooms].name {
    print("因為  rose.residence? 是可空值,并且此時值為 nil, 是以這句話并不會被列印")    //  不執行
    } else {
    print("被列印的話")        //  執行
    }
    //  給一個值為 nil 的屬性指派
    rose.residence?] = Room(roomName: "Living Room")     //  指派失敗,此時 rose.residence?[0] == nil
    rose.residence = Residence()                           //  建立執行個體
    rose.residence?.rooms.append(Room(roomName: "FirstRoom"))       //  添加值
    //  再次嘗試通路下标
    if let firstRoomNameAgain = rose.residence?.rooms].name {
    print("因為  rose.residence? 是可空值,并且此時值為 nil, 是以這句話并不會被列印")    //  執行
    } else {
    print("被列印的話")        //  不執行
    }
               
    如果下标傳回可空類型值,比如 Swift 中

    Dictionary

    key

    下标。可以在下标的閉合括号後面放一個問号來連結下标的可空傳回值:
    var testScrore = ["john": [, , ], "jack": [, , ]]
    testScrore["john"]?[] = 
    testScrore["jack"]?[] = 
    testScrore["Hehe"]?[] =       //  這句并不會執行,去掉問号會報錯
    print(testScrore)      //  輸出: ["jack": [33, 112, 130], "john": [140, 91, 78]]
               
  6. 多層連結

    可以通過多個屬性的可空鍊式調用來向下通路屬性,方法以及下标。但是多層可空鍊式調用不會添加傳回值的可空性,也就是說:

    • 如果通路的值不是可空的,通過可空鍊式調用将會傳回可空值。
    • 如果通路值得值是已經可空的,通過可空鍊式調用并不會變得更空。
    是以:
    • 通過可空鍊式調用通路一個

      Int

      值,将會傳回

      Int?

      ,不論進行了多少次可空鍊式調用。
    • 類似的,通過可空鍊式調用通路

      Int?

      值,并不會變得更空。
  7. 對傳回可空的函數進行連結

    上面的例子說明了如何通過可空鍊式調用來擷取可控的屬性值。其實還可以通過可空鍊式調用來調用傳回值是可空值的方法,并且可以繼續對空值進行連結:

    //  對傳回值可空的方法可以繼續往下連結
    let liLei = Person()
    //  注意,在方法的圓括号後面加問号并不是指方法可空,而是指方法的傳回值可空。
    if let beginWithPrefix = liLei.residence?.address?.buildingIdentifier()?.hasPrefix("China") { 
    } else { 
    }
               

結束語

這一小節其實是有點唬人的,它隻是把我們平常一直用但是沒有太在意的事情說了一遍,是以學起來也是挺輕松的。

我人很懶的,一直學習什麼的也是辦不到。這兩天抽空找了一部動漫看看,絕園的暴風雨,整部動漫圍繞莎士比亞的哈姆雷特、暴風雨兩個複仇劇展開,顯然這麼高大上的書我是沒讀過的,但是動漫本身相當不錯,角色都很有特點。喜歡看動漫的可以補一下。

Swift-可空鍊式調用(Optional Chaining)(十五)