可選鏈是一個查詢和調用可能目前為 nil 的可選屬性、方法和下標過程。如果一個可選包含一個值,那麼屬性、方法跟下標調用將會成功。如果可選為 nil ,那麼屬性、方法跟下標調用將返回 nil 。多個查詢可以鏈在一起,如果鏈中的任何一個節點為 nil ,那麼整個鏈就會失敗。
你可以透過在調用可選項不是 nil 就調用屬性、方法或者腳本的可選值後邊使用問號(?)來明確可選鏈。這與在可選值之後放置驚嘆號(!)以強制解包相似。主要的區別在於可選鏈在可選項為 nil 時會失敗,而強制展開則在可選項為 nil 時觸發運行時錯誤。
為了顯示出可選鏈可以在 nil 值上調用,可選鏈調用的結果一定是一個可選值,就算你查詢的屬性、方法或者下標返回的是非可選值。你可以使用這個可選項返回值來檢查可選鏈調用成功(返回的可選項有值),還是由於鏈中出現了 nil 而導致沒有成功(返回的可選值為 nil )。
另外,可選鏈調用的結果與期望的返回值類型相同,只是包裝成了可選項。像是,返回 Int 的屬性通過可選鏈後會返回一個 Int?。
首先我們先創建兩個 Class , 一個為 Student 和 English ,English 實例有一個 Int 屬性叫做 engScore ,它帶有默認值 60 , Student 實例有一個 English? 類型的可選 score 屬性:
class Student {
var score:English?
}
class English {
var engScore = 60
}
我們創建一個新的 Student 實例,基於可選項的特性,它的 score 會默認初始化為 nil 。所以創建的 Jeremy 的 sroce 值為 nil:
var Jeremy = Student()
如果你這時使用驚嘆號(!)去強制解包,則會運行錯誤,因為沒有值可以展開:
因此我們可以使用 if 方式來判斷其中是否有值,如果其中有則輸出結果,無值則印出 else 的資訊:
if let engTest = Jeremy.score?.engScore {
print("Jeremy的英文分數為\(engTest)")
} else {
print("Jeremy這次英文缺考")
}
這將會告訴Swift把可選 score 屬性“鏈接”起來並且取回 engScore 的值,如果 score 存在的話。
由於嘗試訪問 english 有失敗的可能,可選鏈嘗試返回一個 Int? 類型的值(可選的Int)。當 score 為 nil ,就如同上面的範例,這個可選 Int 將也會是 nil ,來反應出不能訪問 engTest 的問題。可選的 Int 透過可選綁定來展開整數並賦值非可選值給 engTest 。就算 engScore 是非可選的 Int 也是可以的。事實上通過可選鏈查詢就意味著對 engScore 的調用一定會返回 Int? 而不是 Int 。
所以我們這邊將 English 實例賦值給 Jeremy.score ,那麼那就會有默認值 60 :
Jeremy.score = English()
所以我們在執行上面那段程式碼,就會順利輸出他的英文成績:
你可以使用可選鏈來調用屬性、方法和下標不止一個層級。這允許你在相關類型的複雜模型中深入到子屬性,並檢查是否可以在這些自屬性裡訪問屬性、方法和下標。
交易這個 class 與上述範例差不多,只有一個可選類型為 Store? 的 store 變數:
// 交易
class Trade {
var store:Store?
}
商店這個 class 中我們有一個儲存 ShoppingList 實例的數組, numberOfList 則是來儲存數組中 .count 的值,並且我們創建一個下標可以讀寫的來訪問 list 數組的索引位置,作為這個數組賦值的快捷方式,並提供一個方法印出清單數量,最後定義了一個可選類型 customer,類型為 Customer?。
// 商店
class Store {
var list = [ShoppingList]()
var numberOfList:Int {
return list.count
}
subscript(i: Int) -> ShoppingList {
get {
return list[i]
}
set {
list[i] = newValue
}
}
func checkList() {
print("購物清單目前有:\(numberOfList)項物品")
}
var customer:Customer?
}
上面變數 list 數組使用的 ShoppingList 類型設置一個名為 name 的屬性,提供一個初始化器來給這個屬性設置物品名稱:
// 購物清單
class ShoppingList {
let name:String
init(name:String) {
self.name = name
}
}
最後一個創建一個客戶資料的類型 。這個類型有兩個 String ? ,這兩個屬性, name 和 phone ,是定義客戶的姓名以及電話,並提供一個 checkData 的方法,用來輸出客戶資料,裡面有一些判斷式,如果符合要求就印出客戶資訊,如果沒輸入資料或是沒有輸入名稱就輸出一個 "必須輸入用戶姓名資訊" :
// 客戶資料
class Customer {
var name:String?
var phone:String?
func checkData() -> String {
if name != nil , phone == nil{
return "用戶名稱為:\(name!),尚未輸入聯絡電話"
} else if phone != nil && name != nil {
return "用戶名稱為:\(name!),聯絡電話為:\(phone!)"
} else {
return "必須輸入用戶姓名"
}
}
}
你可以使用可選鏈來訪問可選值裡的屬性,檢查這個屬性的訪問是否成功。使用上邊定義的類來創建一個新的 Trade 實例,並訪問它的 numberOfList 屬性:
let trade = Trade()
if let numberOfList = trade.store?.numberOfList {
print("交易清單一有購買\(numberOfList)項物品")
} else {
print("購物清單無物品")
}
但是結果如上面的範例相同,因為 trade.store 為 nil , 所以這個調用失敗了。
我們嘗試透過可選鏈來給屬性賦值:
let someOne = Customer()
someOne.name = "Jeremy"
someOne.phone = "0911234555"
trade.store?.customer = someOne
給 trade.store 的 customer 屬性賦值依然會失敗,因為 trade.store 目前是 nil 。
這個賦值是可選鏈的一部分,也就是說 = 運算符右手側的程式碼都不會被評判。在先前的範例中,不容易看出 someOne 沒有被評判,因為賦值給一個常量不會有任何副作用。
下面我們使用一個函數來創建客戶做同樣的賦值,但我們在函數中加上 "建立客戶資料" 這條訊息,這可以讓你清楚的看到 = 運算符右手側是否被評判。
func createCustomer() -> Customer {
print("建立客戶資料.")
let customer = Customer()
customer.name = "Jeremy"
customer.phone = "0911000222"
trade.store?.customer = customer
return customer
}
john.residence?.address = createAddress()
我們可以看到它並沒有調用 createCustomer() 這個函數,因為沒有訊息被輸出。
結果如下:
你可以使用可選鏈來調用可選項裡的方法,並且檢查調用是否成功。你甚至可以在沒有定義返回值的方法上這麼做。
舉我們 Store 這個 class 中的函數:
func checkList() {
print("購物清單目前有:\(numberOfList)項物品")
}
這個方法沒有指定返回類型,函數和方法如果沒有返回類型就隱式地指明為 Void 類型。意思是說它們返回一個 () 的值或者是一個空的元組。
如果你用可選鏈在可選項裡調用這個方法,方法的返回類型將會是 Void? ,而不是 Void,因為當你通過可選鏈調用的時候返回值一定會是一個可選類型。這允許你使用 if 語句來檢查是否能調用 checkList() 方法,就算是方法自身沒有定義返回值也可以。通過對比調用 checkList 返回的值是否為 nil 來確定方法的調用是否成功:
if trade.store?.checkList() != nil {
print("可以調用此方法")
} else {
print("無法調用此方法")
}
結果如下:
如果你嘗試通過可選鏈來設置屬性也是一樣的。上邊通過可選鏈訪問屬性中的例子嘗試設置 customer 值給 trade.store ,就算是 store 屬性是 nil 也行。任何通過可選鏈設置屬性的嘗試都會返回一個 Void? 類型值,它允許你與 nil 比較來檢查屬性是否設置成功:
if (trade.store?.customer = someOne) != nil {
print("可以成功賦值給 customer ")
} else {
print("無法賦值給 customer")
}
結果如下:
你同樣可以使用可選鏈來給可選項下標訪問或設置值,並且檢查下標的調用是否成功。
我們使用下標訪問 trade.store 屬性的數組 list 裡第一個項目名稱,由於 trade.store 目前是 nil,所以下標調用也會失敗:
if let firstItem = trade.store?.list[0].name {
print("list 中,第一個項目為\(firstItem)")
} else {
print("無法查看 list 第一個項目")
}
結果如下:
同樣的,你可以嘗試在可選鏈裡用下標來設置一個新值,但也還是一樣會無法設置:
trade.store?[0] = ShoppingList(name: "書籍")
如果你創建並且賦值一個實際的 Store 實例給trade.store ,在 list 數組裡添加一個或者多個 ShoppingList 實例,你就可以通過可選鏈使用 Store 下標來訪問 list 數組裡的實際元素了:
let AStore = Store()
AStore.list.append(ShoppingList(name: "書籍"))
AStore.list.append(ShoppingList(name: "漫畫"))
trade.store = AStore
這時候我們再來查看 list 中的第一個項目,結果如下:
如果下標返回是一個可選類型的值,像是 Swift的字典類型的鍵下標——放一個問號在下標的方括號後邊來鏈接它的可選返回值,首先我們先創建一個英文成績,然後來更改字典內的內容:
var engScore = ["Jeremy":[82,98,99],"Allan":[45,67,88] ]
engScore["Jeremy"]?[2] += 1
engScore["Allan"]?[0] = 87
engScore["Curry"]?[0] = 23
修改的第一行是將 Jeremy 中第三個元素加 1 ,所以他的值會變成 100;第二行則是將他第一項的值改為 87;第三行因為沒有 Curry 這個鍵,所以調用失敗