# Swift 可選鏈
可選鏈(Optional Chaining)是一種是一種可以請求和調用屬性、方法和子腳本的過程,用于請求或調用的目標可能為nil。
可選鏈返回兩個值:
* 如果目標有值,調用就會成功,返回該值
* 如果目標為nil,調用將返回nil
多次請求或調用可以被鏈接成一個鏈,如果任意一個節點為nil將導致整條鏈失效。
## 可選鏈可替代強制解析
通過在屬性、方法、或下標腳本的可選值后面放一個問號(?),即可定義一個可選鏈。
| 可選鏈 '?' | 感嘆號(!)強制展開方法,屬性,下標腳本可選鏈 |
| --- | --- |
| ? 放置于可選值后來調用方法,屬性,下標腳本 | ! 放置于可選值后來調用方法,屬性,下標腳本來強制展開值 |
| 當可選為 nil 輸出比較友好的錯誤信息 | 當可選為 nil 時強制展開執行錯誤 |
### 使用感嘆號(!)可選鏈實例
```
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
//將導致運行時錯誤
let roomCount = john.residence!.numberOfRooms
```
以上程序執行輸出結果為:
```
fatal error: unexpectedly found nil while unwrapping an Optional value
```
### 使用感嘆號(!)可選鏈實例
```
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
// 鏈接可選residence?屬性,如果residence存在則取回numberOfRooms的值
if let roomCount = john.residence?.numberOfRooms {
print("John 的房間號為 \(roomCount)。")
} else {
print("不能查看房間號")
}
```
以上程序執行輸出結果為:
```
不能查看房間號
```
因為這種嘗試獲得numberOfRooms的操作有可能失敗,可選鏈會返回Int?類型值,或者稱作"可選Int"。當residence是空的時候(上例),選擇Int將會為空,因此會出現無法訪問numberOfRooms的情況。
要注意的是,即使numberOfRooms是非可選Int(Int?)時這一點也成立。只要是通過可選鏈的請求就意味著最后numberOfRooms總是返回一個Int?而不是Int。
## 為可選鏈定義模型類
你可以使用可選鏈來多層調用屬性,方法,和下標腳本。這讓你可以利用它們之間的復雜模型來獲取更底層的屬性,并檢查是否可以成功獲取此類底層屬性。
### 實例
定義了四個模型類,其中包括多層可選鏈:
```
class Person {
var residence: Residence?
}
// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數組
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房間號為 \(numberOfRooms)")
}
var address: Address?
}
// Room 定義一個name屬性和一個設定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最終類叫做Address
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
}
}
}
```
## 通過可選鏈調用方法
你可以使用可選鏈的來調用可選值的方法并檢查方法調用是否成功。即使這個方法沒有返回值,你依然可以使用可選鏈來達成這一目的。
```
class Person {
var residence: Residence?
}
// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數組
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房間號為 \(numberOfRooms)")
}
var address: Address?
}
// Room 定義一個name屬性和一個設定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最終類叫做Address
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
}
}
}
let john = Person()
if ((john.residence?.printNumberOfRooms()) != nil) {
print("輸出房間號")
} else {
print("無法輸出房間號")
}
```
以上程序執行輸出結果為:
```
無法輸出房間號
```
使用if語句來檢查是否能成功調用printNumberOfRooms方法:如果方法通過可選鏈調用成功,printNumberOfRooms的隱式返回值將會是Void,如果沒有成功,將返回nil。
## 使用可選鏈調用下標腳本
你可以使用可選鏈來嘗試從下標腳本獲取值并檢查下標腳本的調用是否成功,然而,你不能通過可選鏈來設置下標腳本。
### 實例1
```
class Person {
var residence: Residence?
}
// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數組
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房間號為 \(numberOfRooms)")
}
var address: Address?
}
// Room 定義一個name屬性和一個設定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最終類叫做Address
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
}
}
}
let john = Person()
if let firstRoomName = john.residence?[0].name {
print("第一個房間名 \(firstRoomName).")
} else {
print("無法檢索到房間")
}
```
以上程序執行輸出結果為:
```
無法檢索到房間
```
在下標腳本調用中可選鏈的問號直接跟在 circname.print 的后面,在下標腳本括號的前面,因為circname.print是可選鏈試圖獲得的可選值。
### 實例2
實例中創建一個Residence實例給john.residence,且在他的rooms數組中有一個或多個Room實例,那么你可以使用可選鏈通過Residence下標腳本來獲取在rooms數組中的實例了:
```
class Person {
var residence: Residence?
}
// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數組
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房間號為 \(numberOfRooms)")
}
var address: Address?
}
// Room 定義一個name屬性和一個設定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最終類叫做Address
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
}
}
}
let john = Person()
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客廳"))
johnsHouse.rooms.append(Room(name: "廚房"))
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
print("第一個房間名為\(firstRoomName)")
} else {
print("無法檢索到房間")
}
```
以上程序執行輸出結果為:
```
第一個房間名為客廳
```
## 通過可選鏈接調用來訪問下標
通過可選鏈接調用,我們可以用下標來對可選值進行讀取或寫入,并且判斷下標調用是否成功。
### 實例
```
class Person {
var residence: Residence?
}
// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數組
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房間號為 \(numberOfRooms)")
}
var address: Address?
}
// Room 定義一個name屬性和一個設定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最終類叫做Address
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
}
}
}
let john = Person()
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客廳"))
johnsHouse.rooms.append(Room(name: "廚房"))
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
print("第一個房間名為\(firstRoomName)")
} else {
print("無法檢索到房間")
}
```
以上程序執行輸出結果為:
```
第一個房間名為客廳
```
## 訪問可選類型的下標
如果下標返回可空類型值,比如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?值,不論使用了多少層鏈接返回的總是Int?。
### 實例1
下面的例子試圖獲取john的residence屬性里的address的street屬性。這里使用了兩層可選鏈來聯系residence和address屬性,它們兩者都是可選類型:
```
class Person {
var residence: Residence?
}
// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數組
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房間號為 \(numberOfRooms)")
}
var address: Address?
}
// Room 定義一個name屬性和一個設定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最終類叫做Address
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
}
}
}
let john = Person()
if let johnsStreet = john.residence?.address?.street {
print("John 的地址為 \(johnsStreet).")
} else {
print("不能檢索地址")
}
```
以上程序執行輸出結果為:
```
不能檢索地址
```
### 實例2
如果你為Address設定一個實例來作為john.residence.address的值,并為address的street屬性設定一個實際值,你可以通過多層可選鏈來得到這個屬性值。
```
class Person {
var residence: Residence?
}
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("房間號為 \(numberOfRooms)")
}
var address: Address?
}
class Room {
let name: String
init(name: String) { self.name = name }
}
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
}
}
}
let john = Person()
john.residence?[0] = Room(name: "浴室")
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客廳"))
johnsHouse.rooms.append(Room(name: "廚房"))
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
print("第一個房間是\(firstRoomName)")
} else {
print("無法檢索房間")
}
```
以上實例輸出結果為:
```
第一個房間是客廳
```
## 對返回可選值的函數進行鏈接
我們還可以通過可選鏈接來調用返回可空值的方法,并且可以繼續對可選值進行鏈接。
### 實例
```
class Person {
var residence: Residence?
}
// 定義了一個變量 rooms,它被初始化為一個Room[]類型的空數組
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房間號為 \(numberOfRooms)")
}
var address: Address?
}
// Room 定義一個name屬性和一個設定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最終類叫做Address
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
}
}
}
let john = Person()
if john.residence?.printNumberOfRooms() != nil {
print("指定了房間號)")
} else {
print("未指定房間號")
}
```
以上程序執行輸出結果為:
```
未指定房間號
```
- Swift 簡介
- Swift 環境搭建
- Swift 基本語法
- Swift 數據類型
- Swift 變量
- Swift 可選(Optionals)類型
- Swift 常量
- Swift 字面量
- Swift 運算符
- Swift 條件語句
- Swift if 語句
- Swift if...else 語句
- Swift if...else if...else 語句
- Swift 嵌套 if 語句
- Swift switch 語句
- Swift 循環
- Swift for-in 循環
- Swift for 循環
- Swift While 循環
- Swift repeat...while 循環
- Swift Continue 語句
- Swift Break 語句
- Swift Fallthrough 語句
- Swift 字符串
- Swift 字符(Character)
- Swift 數組
- Swift 字典
- Swift 函數
- Swift 閉包
- Swift 枚舉
- Swift 結構體
- Swift 類
- Swift 屬性
- Swift 方法
- Swift 下標腳本
- Swift 繼承
- Swift 構造過程
- Swift 析構過程
- Swift 可選鏈
- Swift 自動引用計數(ARC)
- Swift 類型轉換
- Swift 擴展
- Swift 協議
- Swift 泛型
- Swift 訪問控制