Swift 程式語言 — Optional Chaining
讓我們來看看 Optional Chaining 到底做了些什麼吧!
前言:
Optional chaining 是一個查詢或調用當前可能為 nil 的 optional 屬性、方法和下標的過程。如果 optional 包含一個值,則屬性、方法或下標調用成功;如果 optional 為 nil,則屬性、方法或下標調用返回 nil。可以將多個查詢鏈結在一起,如果鏈中的任何鏈接為 nil,則整個鏈都算是失敗。
Swift 中的 Optional chaining 類似於 Objective-C 中的 messaging nil,但他適用於任何類型,並且可以檢查成功或失敗。
|Optional Chaining 作為強制展開的替代方法
你透過在要調用的屬性、方法和下標後放置問號(?)來指定 optional chaining,如果 optaional 的值不為 nil。這與在 optional 值之後放置驚嘆號(!)來強制展開值非常相似。主要差別是,當 optional 值為 nil 時,Optaion chaining 會優雅的失敗,而強制展開則會觸發 Runtime error。
為了反應可以在 nil 值上調用 optional chaining 的事實,即使你要查詢的屬性、方法和下標返回的是 non-optional 值,Optional chaining 調用的結果也始終是 Optional 值。你可以使用這個 optional 的返回值來檢查 optional chaining 調用是否成功(返回的 optional 包含一個值),或者由於鏈中的值為 nil 而失敗(返回的 optional 為 nil)。
具體來說,Optional chaining 調用的結果與預期返回值的類型相同,但是包裝在 optional 中。當訪問 optional chaining 時,通常返回 Int
的屬性將返回 Int?
。
接下來的幾段程式碼演示了 optional chaining 與強制展開哪裡不同,並使你能夠檢查是否成功。
首先,我們定義了兩個 class
,分別是 Person
和 Residence
:
Residence
實例具有一個名為 numberOfRooms
的 Int
屬性,默認值為 1
。Person
實例具有類型為 Residence?
的 optional Residence
屬性。
如果你創建一個新的 Person
實例,其 residence
屬性由於是可選屬性而默認初始化為 nil
。
在下面的程式碼中,john
的 residence
屬性值為 nil
:
如果你嘗試訪問這個 john
的 residence
的 numberOfRooms
屬性,方法是在 residence
後放置一個感嘆號(!)來強制展開其值,而觸發 Runtime error,因為沒有 residence
的值可以展開:
上面的程式碼在 john.residence
具有非 nil
值時成功,並將 roomCount
設置為包含適當房間數的 Int
值。但是,如上所述,當 residence
為 nil
,此程式碼始終會觸發 Runtime error。
Optional chaining 提供一種訪問 numberOfRooms
值的替代方法。要使用 optional chaining,請使用問號(?)代替驚嘆號(!):
這告訴 Swift 去鏈結在 optional 的 residence
屬性,如果在 residence
存在時的情況下檢索 numberOfRooms
的值。
由於嘗試訪問 numberOfRooms
可能會失敗,因此 optional chaining 嘗試返回 Int?
類型的值或 optional Int
。如上面範例所示,當 residence
為 nil
時,這個 optional Int
也將為 nil
來反映無法訪問 numberOfRooms
的事實。透過 optional binding 訪問 optional Int 可以展開整數並將 non-optional 的值分配給 roomCount
變數。
請注意,即使 numberOfRooms
是 non-optional 的 Int
也是如此。透過 optional chaining 查詢它的事實意味著 numberOfRooms
的調用始終返回 Int?
而不是 Int
。
你可以將 Residence
實例分配給 john.residence
,使其不再具有 nil
值:
john.residence
現在包含一個實際的 Residence
實例,而不是 nil
。如果你嘗試與使用之前相同的 optional chaining 來訪問 numberOfRooms
,現在他將返回一個 Int?
包含默認的 numberOfRooms
值 1
:
|定義 Optional Chaining 的 Model Classes
你可以使用 optional chaining 與深度超過一層的屬性、方法和下標中一起使用。這使你可以深入到相互關聯類型的複雜模型中的子屬性,並且檢查是否可以訪問這些子屬性上的屬性、方法和下標。
下面的程式碼片段定義了四個 modal class 用於後續的幾個範例中,包含多層級 optional chaining 的範例。這些 classes 透過帶有相關屬性、方法和下標的 Room
和 Address
class
,擴展了上面的 Person
和 Residence
model。
Person
class
的定義方式與以前相同:
Residence
class
比之前複雜許多。這次,Residence
class
定義了一個名為 rooms
的變數屬性,該屬性使用 [Room]
類型的空陣列初始化:
因為這個版本的 Residence
儲存了一個陣列的 Room
實例,所以其 numberOfRooms
屬性被實現為計算屬性,而非儲存屬性。計算出 numberOfRooms
屬性只是從 rooms
陣列中返回 count
屬性的值。
作為一個訪問其 rooms
陣列的捷徑,這個版本的 Residence
提供了一個可讀寫的下標,這個下標提供了對 rooms
陣列中 index
的訪問。
這個版本的 Residence
還提供了一個名為 printNumberOfRooms
的方法,該方法只是單純印出該住宅的房間數量。
最後,Residence
定義了一個名為 address
,其類型為 Address?
。這個 Address
類型在下面定義
用於 rooms
陣列的 Room
是一個簡單的 class
,具有一個名為 name
的屬性,以及一個用於將屬性設置為合適的房間名稱的初始化器:
在這個 model 中的最後一個 class
稱為 Address
。這個 class
具有三個 optional 的屬性其類型為 String?
。前面兩個屬性 buildingName
和 buildingNumber
是將特定建築物標誌為地址一部分的替代方法。第三個屬性 street
用來為該地址街道命名。
Address
class
還提供了一個名為 buildingIdentifier
的方法,該方法返回類型 String?
。此方法檢查地址的屬性,如果 buildingName
有值,則返回 buildingName
,如果 buildingNumber
和 street
皆有值,則返回兩者串聯值,否則返回 nil
。
|通過 Optional Chaining 訪問屬性
如同上面 〔Optional Chaining 作為強制展開的替代方法〕所述,可以使用 optional chaining 訪問 optional 值上的屬性,並檢查該屬性訪問是否成功。
使用上面定義的 classes
創建一個新的 Person
實例,然後嘗試像以前一樣訪問其 numberOfRooms
屬性:
由於 john.residence
為 nil
,因此 optional chaining 的調用與之前一樣失敗了。
你還可以嘗試通過 optional chaining 設置屬性的值:
這個範例中,嘗試設置 john.residence
的 address
屬性將會失敗,因為 john.residence
目前為 nil
。
賦值是 optional chaining 的一部分,這意味著 = 運算符號右側的任何程式碼都不會被評估。在上一個範例中,不容易看到 somAddress
從未被評估,因為訪問常數沒有任何副作用。下面的範例執行相同的賦值,但是它是使用一個函數來創建地址。該函數在返回值之前印出了 “Fuction was Called”,這使你可以查看 = 運算符右側的程式碼是否被評估。
你可以說 createAddress
函數沒有被調用,因為沒有印出任何內容。
|通過 Optional chaining 調用方法
你可以使用 optional chaining 來對 optional 值調用方法,並檢查該方法是否調用成功。你可以執行此操作,即使該方法沒有定義回傳值。
Residence
中的 printNumberOfRooms
方法將印出 numberOfRooms
的當前值。該方法看起來如下:
這個方法沒有指定返回類型。但是,沒有返回類型的函數和方法的隱式返回類型為 Void
,如無返回值的函數中所述。這意味著它們的返回值為 ()
或一個空的元組。
如果你透過 optional chaining 對 optional 值調用這個方法,則該方法的返回類型將為 Void?
,而不是 Void
。因為通過 optional chaining 調用時,返回值始終為 optional 類型。這使你能夠使用 if 語法來檢查是否可以調用 printNumberOfRooms
方法,即使該方法本身沒有定義返回值。將 printNumberOfRooms
調用的返回值與 nil
進行比較,來查看該方法是否調用成功:
如果你嘗試通過 optional chaining 設置屬性時,也是如此。上面的〔通過 Optional Chaining 訪問屬性〕中的範例嘗試為 john.residence
設置地址值,即使 Residence
屬性為 nil
。任何通過 optional chaining 設置屬性的嘗試都會返回 Void
類型的值,這使你可以與 nil
進行比較來查看是否成功設置了該屬性:
|通過 Optional Chaining 訪問下標
你可以使用 optional chaining 來嘗試從下標中檢索和設置 optional 的值,並檢查該下標調用是否成功。
當你通過 optional chaining 來對 optional value 訪問下標時,將問號放在下標方括號之前,而不是之後。Optional chaining 的問號總是緊接在 表達式的 optional 部分之後。
下面的範例嘗試使用 Residence
定義的下標檢索 john.residence
屬性的 rooms
陣列中第一個房間的名稱。因為 john.residence
當前為 nil
,所以下標調用失敗:
這個下標調用中的 optional chaining 的問號放在 john.residence
之後,並在下標括號之前。因為 john.residence
是 optional 嘗試進行 optional chaining 的 optional 值。
同樣的,你可以嘗試通過帶有 optional chaining 的下標設置新的值:
這個下標設置嘗試也會失敗,因為 residence
目前為 nil
。
如果你創建一個實際的 Residence
實例,並且將其分配給 john.residence
,並且在 rooms
陣列中加入一個或多的 Room
實例,則可以使用 Residence
下標通過 optional chaining 訪問 rooms
陣列中的實際項目:
|訪問 Optional 類型的下標
如果下標返回的是 optional 類型的值(像是 Swift 的字典類型的 key
下標),則在下標的右括號後放置一個問號,以鏈接到其 optional 的返回值:
上面的範例定義了一個名為 testScores
的字典,該字典包含兩個 String
key 映射到 [Int]
value 的 key-value 組合。這個範例使用 optional chaining 將 “Dave”
陣列中的第一個項目設置為 91
;將 “Bev”
陣列中的第一個項目增加 1
;並且嘗試為 key 為 “Brain”
設置陣列中的第一個項目。前兩次調用成功,因為 testScores
字典包含 “Dave”
和 “Bev”
的 key。第三次調用失敗,因為 testScores
字典不包含 "Brain”
的 key。
|連接多的層級的鏈
你可以將多個層級的 optional chaining 連接再一起,來深入到 Model 中更深的屬性、方法和下標中。但是,多個層級的 optional chaing 不會為返回的值添加更多層級的可選性。
也就是說:
- 如果你嘗試檢索的類型不是 optional 的,則由於 optional chaining,它變為 optional 的。
- 如果你嘗試檢索的類型已經是 optional 的,他不會因為 optional chaining 變得更 optional。
因此:
- 如果你嘗試通過 optional chaining 檢索
Int
值,無論有多少層級的鏈接,總是返回Int?
。 - 同樣的,如果通過你嘗試 optional 檢索
Int?
值,無論有多少層級的鏈接,總是返回Int?
。
下面的範例嘗試訪問 john
的 Residence
屬性中 address
屬性中的 street
屬性。這邊使用兩個層級的 optional chaining,來鏈接 residence
和 address
屬性,這兩者皆為 optional 類型:
john.residence
的值當前包含有效的 Residence
實例。但是,john.residence.address
的值為 nil
。因此,對 john.residence?.address?.street
的調用失敗。
請注意,在上面的範例中,你嘗試檢索 street
屬性的值。這個屬性的類型為 String?
。因此,即使除了屬性的基礎 optional 類型之外還應用了兩個層級的 optional chaining,john.residence?.address?.street
的返回值也是 String?
。
如果你將實際的 Address
實例設置為 john.residence.address
的值,並為該地址的 street
屬性設置了實際值,則可以通過多層級 optional chaining 訪問 street
屬性的值:
這個範例中,設置 john.residence
的 address
屬性的嘗試將會成功,因為 john.residence
的值當前包含有效的 Residence
實例。
|鏈結具有 Optional 返回值的方法
前面的範例演示了如何通過 optional chaining 檢索 optional 類型的屬性值。你還可以使用 optional chaining 來調用返回 optional 類型值的方法,並根據需要鏈接該方法的返回值。
下面的範例通過 optional chaining 調用 Address
class
中的 buildingIdentifier
方法。這個方法返回 String?
類型的值。如上所述,在 optional chaining 後,此方法調用的最終返回類型也是 String?
:
如果你想要對該方法的返回值執行進一步的 optional chaining,請在該方法的括號後放置 optional chaining 的問號:
在上面的範例中,你將 optional chaining 的問號放在括號後,因為要鏈接的 optional 值是 buildingIdentifier 方法的返回值,而不是 buildingIdentifier 方法本身。