可選鏈式調用 是一種可以在當前值可能為 nil 的可選值上請求和調用屬性璧亚、方法及下標的方法。如果可選值有值,那么調用就會成功;如果可選值是 nil ,那么調用將返回 nil 。多個調用可以連接在一起形成一個調用 鏈铅忿,如果其中任何一個節(jié)點為 nil 剪决,整個調用鏈都會失敗,即返回 nil 檀训。
注意
Swift 的可選鏈式調用和 Objective-C 中向 nil 發(fā)送消息有些相像柑潦,但是 Swift 的可選鏈式調用可以應用于任意類型,并且能檢查調用是否成功峻凫。
使用可選鏈式調用代替強制展開
通過在想調用的屬性渗鬼、方法、或下標的可選值后面放一個問號( ? )荧琼,可以定義一個可選鏈譬胎。這一點很像在可選 值后面放一個嘆號( ! )來強制展開它的值。它們的主要區(qū)別在于當可選值為空時可選鏈式調用只會調用失 敗命锄,然而強制展開將會觸發(fā)運行時錯誤堰乔。
為了反映可選鏈式調用可以在空值( nil )上調用的事實,不論這個調用的屬性脐恩、方法及下標返回的值是不是可選值镐侯,它的返回結果都是一個可選值。你可以利用這個返回值來判斷你的可選鏈式調用是否調用成功驶冒,如果調用 有返回值則說明調用成功苟翻,返回 nil 則說明調用失敗。
特別地骗污,可選鏈式調用的返回結果與原本的返回結果具有相同的類型崇猫,但是被包裝成了一個可選值。例如需忿,使用 可選鏈式調用訪問屬性邓尤,當可選鏈式調用成功時,如果屬性原本的返回結果是 Int 類型,則會變?yōu)?Int? 類型汞扎。
下面幾段代碼將解釋可選鏈式調用和強制展開的不同季稳。
首先定義兩個類 Person 和 Residence :
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
Residence 有一個 Int 類型的屬性 numberOfRooms ,其默認值為 1 澈魄。 Person 具有一個可選的 residence 屬性景鼠,其類型為 Residence? 。
假如你創(chuàng)建了一個新的 Person 實例,它的 residence 屬性由于是是可選型而將初始化為 nil ,在下面的代碼中, john 有一個值為 nil 的 residence 屬性:
let john = Person()
如果使用嘆號( ! )強制展開獲得這個 john 的 residence 屬性中的 numberOfRooms 值痹扇,會觸發(fā)運行時錯誤铛漓,因為 這時 residence 沒有可以展開的值:
let roomCount = john.residence!.numberOfRooms
// 這會引發(fā)運行時錯誤
john.residence 為非 nil 值的時候,上面的調用會成功鲫构,并且把 roomCount 設置為 Int 類型的房間數量浓恶。正如上 面提到的,當 residence 為 nil 的時候上面這段代碼會觸發(fā)運行時錯誤结笨。
可選鏈式調用提供了另一種訪問 numberOfRooms 的方式包晰,使用問號( ? )來替代原來的嘆號( ! ):
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印 “Unable to retrieve the number of rooms.”
在 residence 后面添加問號之后,Swift 就會在 residence 不為 nil 的情況下訪問 numberOfRooms 炕吸。
因為訪問 numberOfRooms 有可能失敗伐憾,可選鏈式調用會返回 Int? 類型,或稱為“可選的 *Int *”赫模。如上例所示树肃,當 residence 為 nil 的時候,可選的 Int 將會為 nil 瀑罗,表明無法訪問 numberOfRooms 胸嘴。訪問成功時,可選的 Int 值會通過可選綁定展開斩祭,并賦值給非可選類型的 roomCount 常量筛谚。
要注意的是,即使 numberOfRooms 是非可選的 Int 時停忿,這一點也成立驾讲。只要使用可選鏈式調用就意味著 numberOfRooms 會返回一個 Int? 而不是 Int 。
可以將一個 Residence 的實例賦給 john.residence 席赂,這樣它就不再是 nil 了:
john.residence = Residence()
john.residence 現在包含一個實際的 Residence 實例吮铭,而不再是 nil 。如果你試圖使用先前的可選鏈式調用訪問 numberOfRooms 颅停,它現在將返回值為 1 的 Int? 類型的值:
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印 “John's residence has 1 room(s).”
為可選鏈式調用定義模型類
通過使用可選鏈式調用可以調用多層屬性谓晌、方法和下標。這樣可以在復雜的模型中向下訪問各種子屬性癞揉,并且判斷能否訪問子屬性的屬性纸肉、方法或下標溺欧。
下面這段代碼定義了四個模型類,這些例子包括多層可選鏈式調用柏肪。為了方便說明姐刁,在 Person 和 Residence 的基礎上增加了 Room 類和 Address 類,以及相關的屬性烦味、方法以及下標聂使。
Person 類的定義基本保持不變:
class Person {
var residence: Residence?
}
Residence 類比之前復雜些,增加了一個名為 rooms 的變量屬性谬俄,該屬性被初始化為 [Room] 類型的空數組:
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
get {
return rooms[i]
}
set {
rooms[i] = newValue
}
}
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
var address: Address?
}
現在 Residence 有了一個存儲 Room 實例的數組柏靶,numberOfRooms 屬性被實現為計算型屬性,而不是存儲型屬性溃论。 numberOfRooms 屬性簡單地返回 rooms 數組的 count 屬性的值屎蜓。
Residence 還提供了訪問 rooms 數組的快捷方式,即提供可讀寫的下標來訪問 rooms 數組中指定位置的元素钥勋。此外炬转,* Residence* 還提供了 printNumberOfRooms 方法,這個方法的作用是打印 numberOfRooms 的值笔诵。
此外,Residence 還提供了 printNumberOfRooms 方法姑子,這個方法的作用是打印 numberOfRooms 的值乎婿。
最后,Residence 還定義了一個可選屬性 address街佑,其類型為 Address?谢翎。
Room 類是一個簡單類,其實例被存儲在 rooms 數組中沐旨。該類只包含一個屬性 name森逮,以及一個用于將該屬性設置為適當的房間名的初始化函數:
class Room {
let name: String
init(name: String) { self.name = name }
}
最后一個類是 Address,這個類有三個 String? 類型的可選屬性磁携。buildingName 以及 buildingNumber 屬性分別表示某個大廈的名稱和號碼褒侧,第三個屬性 street 表示大廈所在街道的名稱:
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if buildingName != nil {
return buildingName
} else if buildingNumber != nil && street != nil {
return "\(buildingNumber) \(street)"
} else {
return nil
}
}
}
Address 類提供了 buildingIdentifier() 方法,返回值為 String?谊迄,如果 buildingName 有值則返回 buildingName闷供。或者统诺,如果 buildingNumber 和 street 均有值則返回 buildingNumber歪脏。否則,返回 nil粮呢。
通過可選鏈式調用訪問屬性
可以通過可選鏈式調用在一個可選值上訪問它的屬性婿失,并判斷訪問是否成功钞艇。
下面的代碼創(chuàng)建了一個 Person 實例,然后像之前一樣豪硅,嘗試訪問 numberOfRooms 屬性:
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印 “Unable to retrieve the number of rooms.”
因為 john.residence 為 nil 哩照,所以這個可選鏈式調用依舊會像先前一樣失敗。
還可以通過可選鏈式調用來設置屬性值:
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress
在這個例子中舟误,通過 john.residence 來設定 address 屬性也會失敗葡秒,因為 john.residence 當前為 nil。
上面代碼中的賦值過程是可選鏈式調用的一部分嵌溢,這意味著可選鏈式調用失敗時眯牧,等號右側的代碼不會被執(zhí)行。對于上面的代碼來說赖草,很難驗證這一點学少,因為像這樣賦值一個常量沒有任何副作用。下面的代碼完成了同樣 的事情秧骑,但是它使用一個函數來創(chuàng)建 Address 實例版确,然后將該實例返回用于賦值。該函數會在返回前打印“Funct ion was called”乎折,這使你能驗證等號右側的代碼是否被執(zhí)行绒疗。
func createAddress() -> Address {
print("Function was called.")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
john.residence?.address = createAddress()
沒有任何打印消息,可以看出 createAddress() 函數并未被執(zhí)行骂澄。
通過可選鏈式調用調用方法
可以通過可選鏈式調用來調用方法吓蘑,并判斷是否調用成功,即使這個方法沒有返回值坟冲。 Residence 類中的 printNumberOfRooms() 方法打印當前的 numberOfRooms 值磨镶,如下所示:
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
這個方法沒有返回值。然而健提,沒有返回值的方法具有隱式的返回類型 Void琳猫。這 意味著沒有返回值的方法也會返回 () ,或者說空的元組私痹。
如果在可選值上通過可選鏈式調用來調用這個方法脐嫂,該方法的返回類型會是 Void? ,而不是 Void 紊遵,因為通過可選 鏈式調用得到的返回值都是可選的雹锣。這樣我們就可以使用 if 語句來判斷能否成功調用 printNumberOfRooms() 方法,即使方法本身沒有定義返回值癞蚕。通過判斷返回值是否為 nil 可以判斷調用是否成功:
if john.residence?.printNumberOfRooms() != nil {
print("It was possible to print the number of rooms.")
} else {
print("It was not possible to print the number of rooms.")
}
// 打印 “It was not possible to print the number of rooms.”
同樣的蕊爵,可以據此判斷通過可選鏈式調用為屬性賦值是否成功。通過可選鏈式調用給屬性賦值會返回 Void? 桦山,通過判斷返回值是否為 nil 就可以知道賦值是否成功:
if (john.residence?.address = someAddress) != nil {
print("It was possible to set the address.")
} else {
print("It was not possible to set the address.")
}
// 打印 “It was not possible to set the address.”
通過可選鏈式調用訪問下標
通過可選鏈式調用攒射,我們可以在一個可選值上訪問下標醋旦,并且判斷下標調用是否成功。
注意
通過可選鏈式調用訪問可選值的下標時会放,應該將問號放在下標方括號的前面而不是后面饲齐。可選鏈式調用的問號一般直接跟在可選表達式的后面咧最。
下面這個例子用下標訪問 john.residence 屬性存儲的 Residence 實例的 rooms 數組中的第一個房間的名稱捂人,因為 john.residence 為 nil ,所以下標調用失敗了:
if let firstRoomName = john.residence?[0].name {
print("The first room name is \(firstRoomName).")
} else {
print("Unable to retrieve the first room name.")
}
// 打印 “Unable to retrieve the first room name.”
在這個例子中矢沿,問號直接放在 john.residence 的后面滥搭,并且在方括號的前面,因為 john.residence 是可選值捣鲸。 類似的瑟匆,可以通過下標,用可選鏈式調用來賦值:
john.residence?[0] = Room(name: "Bathroom")
這次賦值同樣會失敗栽惶,因為 residence 目前是 nil愁溜。
如果你創(chuàng)建一個 Residence 實例,并為其 rooms 數組添加一些 Room 實例外厂,然后將 Residence 實例賦值給 john.residence冕象,那就可以通過可選鏈和下標來訪問數組中的元素:
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "Living Room"))
johnsHouse.rooms.append(Room(name: "Kitchen"))
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
print("The first room name is \(firstRoomName).")
} else {
print("Unable to retrieve the first room name.")
}
// 打印 “The first room name is Living Room.”
訪問可選類型的下標
如果下標返回可選類型值,比如 Swift 中 類型的鍵的下標汁蝶,可以在下標的結尾括號后面放一個問號 來在其可選返回值上進行可選鏈式調用:
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// "Dave" 數組現在是 [91, 82, 84]渐扮,"Bev" 數組現在是 [80, 94, 81]
上面的例子中定義了一個 testScores 數組,包含了兩個鍵值對穿仪,把 String 類型的鍵映射到一個 Int 值得數組席爽。這個例子用可選鏈式調用把 ”Dave“ 數組中的第一個元素設為 91意荤,把 ”Bev“ 數組的第一個元素 +1啊片,然后嘗試把 ”Brian“ 數組中的第一個元素設為 72。前兩個調用成功玖像,因為 testScores 字典中包含 ”Dave“ 和 ”Bev“ 這兩個鍵紫谷。但是 testScores 字典中沒有 ”Brian“ 這個鍵,所以第三個調用失敗捐寥。
連接多層可選鏈式調用
可以通過連接多個可選鏈式調用在更深的模型層級中訪問屬性笤昨、方法以及下標。然而握恳,多層可選鏈式調用不會增加返回值的可選層級瞒窒。
也就是說:
- 如果你訪問的值不是可選的,可選鏈式調用將會返回可選值乡洼。
- 如果你訪問的值就是可選的崇裁,可選鏈式調用不會讓可選返回值變得“更可選”匕坯。
因此:
- 通過可選鏈式調用訪問一個 Int 值,將會返回 Int? 拔稳,無論使用了多少層可選鏈式調用葛峻。
- 類似的,通過可選鏈式調用訪問 Int? 值巴比,依舊會返回 Int? 值术奖,并不會返回 Int?? 。
下面的例子嘗試訪問 john 中的 residence 屬性中的 address 屬性中的 street 屬性轻绞。這里使用了兩層可選鏈式調用采记, residence 以及 address 都是可選值:
if let johnsStreet = john.residence?.address?.street {
print("John's street name is \(johnsStreet).")
} else {
print("Unable to retrieve the address.")
}
// 打印 “Unable to retrieve the address.”
john.residence 現在包含一個有效的 Residence 實例。然而铲球, john.residence.address 的值當前為 nil 挺庞。因此,調用 john.residence?.address?.street 會失敗稼病。
需要注意的是选侨,上面的例子中, street 的屬性為 String? 然走。 john.residence?.address?.street 的返回值也依然是 String? 援制,即使已經使用了兩層可選鏈式調用。
如果為 john.residence.address 賦值一個 Address 實例芍瑞,并且為 address 中的 street 屬性設置一個有效值晨仑,我們就能過通過可選鏈式調用來訪問 street 屬性:
let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress
if let johnsStreet = john.residence?.address?.street {
print("John's street name is \(johnsStreet).")
} else {
print("Unable to retrieve the address.")
}
// 打印 “John's street name is Laurel Street.”
在上面的例子中,因為 john.residence 包含一個有效的 Residence 實例拆檬,所以對 john.residence 的 address 屬性賦值將會成功洪己。
在方法的可選返回值上進行可選鏈式調用
上面的例子展示了如何在一個可選值上通過可選鏈式調用來獲取它的屬性值。我們還可以在一個可選值上通過可選鏈式調用來調用方法竟贯,并且可以根據需要繼續(xù)在方法的可選返回值上進行可選鏈式調用答捕。
在下面的例子中,通過可選鏈式調用來調用 Address 的 buildingIdentifier() 方法屑那。這個方法返回 String? 類型的值拱镐。如上所述,通過可選鏈式調用來調用該方法持际,最終的返回值依舊會是 String? 類型:
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
print("John's building identifier is \(buildingIdentifier).")
}
// 打印 “John's building identifier is The Larches.”
如果要在該方法的返回值上進行可選鏈式調用沃琅,在方法的圓括號后面加上問號即可:
if let beginsWithThe =
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
if beginsWithThe {
print("John's building identifier begins with \"The\".")
} else {
print("John's building identifier does not begin with \"The\".")
}
}
// 打印 “John's building identifier begins with "The".”
注意
在上面的例子中,在方法的圓括號后面加上問號是因為你要在 buildingIdentifier() 方法的可選返回值上進行可選鏈式調用蜘欲,而不是方法本身益眉。