> 1.0 翻譯:[Jasonbroker](https://github.com/Jasonbroker)?校對:[numbbbbb](https://github.com/numbbbbb),?[stanzhai](https://github.com/stanzhai)
>
> 2.0 翻譯+校對:[lyojo](https://github.com/lyojo)
本頁內容包含:
[TOC=2]
可空鏈式調用(Optional Chaining)是一種可以請求和調用屬性、方法及下標的過程,它的可空性體現于請求或調用的目標當前可能為空(nil)。如果可空的目標有值,那么調用就會成功;如果選擇的目標為空(nil),那么這種調用將返回空(nil)。多個連續的調用可以被鏈接在一起形成一個調用鏈,如果其中任何一個節點為空(nil)將導致整個鏈調用失敗。
> 注意: Swift 的可空鏈式調用和 Objective-C 中的消息為空有些相像,但是 Swift 可以使用在任意類型中,并且能夠檢查調用是否成功。
## 使用可空鏈式調用來強制展開
通過在想調用非空的屬性、方法、或下標的可空值(optional value)后面放一個問號,可以定義一個可空鏈。這一點很像在可空值后面放一個嘆號(!)來強制展開其中值。它們的主要的區別在于當可空值為空時可空鏈式只是調用失敗,然而強制展開將會觸發運行時錯誤。
為了反映可空鏈式調用可以在空對象(nil)上調用,不論這個調用的屬性、方法、下標等返回的值是不是可空值,它的返回結果都是一個可空值。你可以利用這個返回值來判斷你的可空鏈式調用是否調用成功,如果調用有返回值則說明調用成功,返回`nil`則說明調用失敗。
特別地,可空鏈式調用的返回結果與原本的返回結果具有相同的類型,但是被包裝成了一個可空類型值。當可空鏈式調用成功時,一個本應該返回`Int`的類型的結果將會返回`Int?`類型。
下面幾段代碼將解釋可空鏈式調用和強制展開的不同。 首先定義兩個類`Person`和`Residence`。
~~~
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
~~~
`Residence`有一個`Int`類型的屬性`numberOfRooms`,其默認值為1。`Person`具有一個可空的`residence`屬性,其類型為`Residence?`。
如果創建一個新的`Person`實例,因為它的`residence`屬性是可空的,`john`屬性將初始化為`nil`:
~~~
let john = Person()
~~~
如果使用嘆號(!)強制展開獲得這個`john`的`residence`屬性中的`numberOfRooms`值,會觸發運行時錯誤,因為這時沒有可以展開的`residence`:
~~~
let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error
~~~
`john.residence`非空的時候,上面的調用成功,并且把`roomCount`設置為`Int`類型的房間數量。正如上面說到的,當`residence`為空的時候上面這段代碼會觸發運行時錯誤。
可空鏈式調用提供了一種另一種訪問`numberOfRooms`的方法,使用問號(?)來代替原來嘆號(!)的位置:
~~~
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// prints "Unable to retrieve the number of rooms."
~~~
在`residence`后面添加問號之后,Swift就會在`residence`不為空的情況下訪問`numberOfRooms`。
因為訪問`numberOfRooms`有可能失敗,可空鏈式調用會返回`Int?`類型,或稱為“可空的Int”。如上例所示,當`residence`為`nil`的時候,可空的`Int`將會為`nil`,表明無法訪問`numberOfRooms`。
要注意的是,即使`numberOfRooms`是不可空的`Int`時,這一點也成立。只要是通過可空鏈式調用就意味著最后`numberOfRooms`返回一個`Int?`而不是`Int`。
通過賦給`john.residence`一個`Residence`的實例變量:
~~~
john.residence = Residence()
~~~
這樣`john.residence`不為`nil`了。現在就可以正常訪問`john.residence.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.")
}
// prints "John's residence has 1 room(s)."
~~~
## 為可空鏈式調用定義模型類
通過使用可空鏈式調用可以調用多層屬性,方法,和下標。這樣可以通過各種模型向下訪問各種子屬性。并且判斷能否訪問子屬性的屬性,方法或下標。
下面這段代碼定義了四個模型類,這些例子包括多層可空鏈式調用。為了方便說明,在`Person`和`Residence`的基礎上增加了`Room`和`Address`,以及相關的屬性,方法以及下標。
Person類定義基本保持不變:
~~~
class Person {
var residence: Residence?
}
~~~
`Residence`類比之前復雜些,增加了一個`Room`類型的空數組`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`數組的快捷方式, 通過可讀寫的下標來訪問指定位置的數組元素。此外,還提供`printNumberOfRooms`方法,這個方法的作用就是輸出這個房子中房間的數量。最后,`Residence`定義了一個可空屬性`address`,其類型為`Address?`。`Address`類的定義在下面會說明。
類`Room`是一個簡單類,只包含一個屬性`name`,以及一個初始化函數:
~~~
class Room {
let name: String
init(name: String) { self.name = name }
}
~~~
最后一個類是`Address`,這個類有三個`String?`類型的可空屬性。`buildingName`以及`buildingNumber`屬性表示建筑的名稱和號碼,用來表示某個特定的建筑。第三個屬性表示建筑所在街道的名稱:
~~~
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if buildingName != nil {
return buildingName
} else if buildingNumber != nil {
return buildingNumber
} else {
return nil
}
}
}
~~~
類`Address`提供`buildingIdentifier()`方法,返回值為`String?`。 如果`buildingName`不為空則返回`buildingName`, 如果`buildingNumber`不為空則返回`buildingNumber`。如果這兩個屬性都為空則返回`nil`。
## 通過可空鏈式調用訪問屬性
正如[使用可空鏈式調用來強制展開](http://wiki.jikexueyuan.com/project/swift/chapter2/17_Optional_Chaining.html#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通過可空鏈式調用訪問屬性的可空值,并且判斷訪問是否成功。
下面的代碼創建了一個`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.")
}
// prints "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`。
## 通過可空鏈式調用來調用方法
可以通過可空鏈式調用來調用方法,并判斷是否調用成功,即使這個方法沒有返回值。?`Residence`中的`printNumberOfRooms()`方法輸出當前的`numberOfRooms`值:
~~~
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
~~~
這個方法沒有返回值。但是沒有返回值的方法隱式返回`Void`類型,如[無返回值函數](http://wiki.jikexueyuan.com/project/swift/chapter2/06_Functions.html#functions_without_return_values)中所述。這意味著沒有返回值的方法也會返回()或者空的元組。
如果在可空值上通過可空鏈式調用來調用這個方法,這個方法的返回類型為`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.")
}
// prints "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.")
}
// prints "It was not possible to set the address."
~~~
## 通過可空鏈式調用來訪問下標
通過可空鏈式調用,我們可以用下標來對可空值進行讀取或寫入,并且判斷下標調用是否成功。
> 注意: 當通過可空鏈式調用訪問可空值的下標的時候,應該將問號放在下標方括號的前面而不是后面。可空鏈式調用的問號一般直接跟在可空表達式的后面。
下面這個例子用下標訪問`john.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.")
}
// prints "Unable to retrieve the first room name."
~~~
在這個例子中,問號直接放在`john.residence`的后面,并且在方括號的前面,因為`john.residence`是可空值。
類似的,可以通過下標,用可空鏈式調用來賦值:
~~~
john.residence?[0] = Room(name: "Bathroom")
~~~
這次賦值同樣會失敗,因為`residence`目前是`nil`。
如果你創建一個`Residence`實例,添加一些`Room`實例并賦值給`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.")
}
// prints "The first room name is Living Room."
~~~
## 訪問可空類型的下標:
如果下標返回可空類型值,比如Swift中`Dictionary`的`key`下標。可以在下標的閉合括號后面放一個問號來鏈接下標的可空返回值:
~~~
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0]++
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
~~~
上面的例子中定義了一個`testScores`數組,包含了兩個鍵值對, 把`String`類型的`key`映射到一個整形數組。這個例子用可空鏈式調用把“Dave”數組中第一個元素設為91,把”Bev”數組的第一個元素+1,然后嘗試把”Brian”數組中的第一個元素設為72。前兩個調用是成功的,因為這兩個`key`存在。但是key“Brian”在字典中不存在,所以第三個調用失敗。
## 多層鏈接
可以通過多個鏈接多個可空鏈式調用來向下訪問屬性,方法以及下標。但是多層可空鏈式調用不會添加返回值的可空性。
也就是說:
* 如果你訪問的值不是可空的,通過可空鏈式調用將會放回可空值。
* 如果你訪問的值已經是可空的,通過可空鏈式調用不會變得“更”可空。
因此:
* 通過可空鏈式調用訪問一個`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.")
}
// prints "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`中的`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.")
}
// prints "John's street name is Laurel Street."
~~~
在上面的例子中,因為`john.residence`是一個可用的`Residence`實例,所以對`john.residence`的`address`屬性賦值成功。
## 對返回可空值的函數進行鏈接
上面的例子說明了如何通過可空鏈式調用來獲取可空屬性值。我們還可以通過可空鏈式調用來調用返回可空值的方法,并且可以繼續對可空值進行鏈接。
在下面的例子中,通過可空鏈式調用來調用`Address`的`buildingIdentifier()`方法。這個方法返回`String?`類型。正如上面所說,通過可空鏈式調用的方法的最終返回值還是`String?`:
~~~
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
print("John's building identifier is \(buildingIdentifier).")
}
// prints "John's building identifier is The Larches."
~~~
如果要進一步對方法的返回值進行可空鏈式調用,在方法`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\".")
}
}
// prints "John's building identifier begins with "The"."
~~~
> 注意: 在上面的例子中在,在方法的圓括號后面加上問號是因為`buildingIdentifier()`的返回值是可空值,而不是方法本身是可空的。
- 介紹
- 歡迎使用 Swift
- 關于 Swift
- Swift 初見
- Swift 版本歷史記錄
- Swift1.0 發布內容
- Swift 教程
- 基礎部分
- 基本運算符
- 字符串和字符
- 集合類型
- 控制流
- 函數
- 閉包
- 枚舉
- 類和結構體
- 屬性
- 方法
- 下標腳本
- 繼承
- 構造過程
- 析構過程
- 自動引用計數
- 可選鏈
- 錯誤處理
- 類型轉換
- 嵌套類型
- 擴展
- 協議
- 泛型
- 權限控制
- 高級操作符
- 語言參考
- 關于語言參考
- 詞法結構
- 類型
- 表達式
- 語句
- 聲明
- 特性
- 模式
- 泛型參數
- 語法總結
- 蘋果官方Blog官方翻譯
- Access Control 權限控制的黑與白
- 造個類型不是夢-白話Swift類型創建
- WWDC里面的那個“大炮打氣球”
- Swift與C語言指針友好合作
- 引用類型和值類型的恩怨
- 訪問控制和Protected
- 可選類型完美解決占位問題