可選鏈式調用是一種可以在當前值可能爲nil的可選值上請求和調用屬性、方法及下標的方法。如果可選值有值,那麼調用就會成功;如果可選值是nil,那麼調用將返回nil。多個調用可以連接在一起形成一個調用鏈,如果其中任何一個節點爲nil,整個調用鏈都會失敗,即返回nil。
使用可選鏈式調用代替強制展開
class Person {
var residence:Residence?
}
class Residence {
var numberOfRooms = 1
}
假如創建一個新的Person實例,它的residence屬性由於是可選類型而將初始化爲nil,在下面的代碼中,John有一個值爲nil的residence屬性: let john = Person()
如果使用歎號強制展開獲得這個john的residence屬性中的numberOfRooms值,就會觸發運行時的錯誤,因爲這時residence沒有可以展開的值: let roomCount = john.residence!.numberOfRooms
john.residence爲非nil值的時候,上面的調用會成功,並且把roomCount設置爲Int類型的房間數量。正如上面提到的,當residence爲nil的時候上面這段代碼會觸發運行時錯誤。 if let roonCount = john.residence?.numberOfRooms {
}else{
}
在residence後面添加問號之後,swift就會在residence不爲nil的情況下訪問numberOfRooms。john.residence = Residence()
爲可選鏈式調用定義模型類
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還定義了一個可選屬性address,其類型爲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
} }
}
通過可選鏈式調用訪問屬性
下面的代碼創建了一個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.")
}
因爲john.residence爲nil,所以這個可選鏈式調用依舊會像先前一樣失敗。
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress
在這個例子中,通過john.residence來設定address屬性也會失敗,因爲john.residence當前爲nil。
上面代碼中的賦值過程是可選鏈式調用的一部分,這意味着可選鏈式調用失敗時,等號右側的代碼不會被執行。對於上面的代碼來說,很難驗證這一點,因爲像這樣賦值一個常量沒有任何副作用。下面的代碼完成了同樣的事情,但是它使用一個函數來創建Address實例,然後將該實例返回用於賦值。該函數會在返回前打印“Function was called”,這使你能驗證等號右側的代碼是否被執行。
func createAddress() -> Address {
print("Function was called.")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
john.residence?.address = createAddress()
沒有任何打印消息,可以看出createAddress()函數並未被執行。
通過可選鏈式調用調用方法
可以通過可選鏈式調用來調用方法,並判斷是否調用成功,即使這個方法沒有返回值。Residence類中的printNumberOfRooms()方法打印當前的numberOfRooms值,如下所示:
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
如果在可選值上通過可選鏈式調用來調用這個方法,該方法的返回類型會是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.")
}
我們嘗試給john.residence中的address屬性賦值,即使residence爲nil。通過可選鏈式調用給屬性賦值會返回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.")
}
通過可選鏈式調用訪問下標
下面這個例子用下標訪問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.")
}
在這個例子中,問號直接放在john.residence的後面,並且在方括號的前面,因爲john.residence是可選值。類似的,可以通過下標,用可選鏈式調用來賦值:
john.residence?[0] = Room(name: "Bathroom")
這次賦值同樣會失敗,因爲residence目前是nil。
如果你創建一個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.")
}
訪問可選類型的下標
如果下標返回可選類型值,比如 Swift 中 類型的鍵的下標,可以在下標的結尾括號後面放一個問號來在其可選返回值上進行可選鏈式調用:
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
前兩個調用可以成功,但是在字典中沒有“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.")
}
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.")
}
在方法的可選返回值上進行可選鏈式調用
上面的例子展示瞭如何在一個可選值上通過可選鏈式調用來獲取它的屬性值。我們還可以在一個可選值上通過可
選鏈式調用來調用方法,並且可以根據需要繼續在方法的可選返回值上進行可選鏈式調用。
在下面的例子中,通過可選鏈式調用來調用 Address 的 buildingIdentifier() 方法。這個方法返回 String? 類型的值。如上所述,通過可選鏈式調用來調用該方法,最終的返回值依舊會是 String? 類型:
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
print("John's building identifier is \(buildingIdentifier).")
}
如果要在該方法的返回值上進行可選鏈式調用,在方法的圓括號後面加上問號即可:
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\".")
} }
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\".")
}}