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("未指定房间号") }
以上程式執行輸出結果為:
未指定房间号