標籤:ios swift
可選鏈(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屬性由於是被定義為自判斷型的,此屬性將預設初始化為空白:
let john = Person()
如果你想使用驚嘆號(!)強制解析獲得這個人residence屬性numberOfRooms屬性值,將會引發執行階段錯誤,因為這時沒有可以供解析的residence值。
let roomCount =john.residence!.numberOfRooms
//將導致執行階段錯誤
當john.residence不是nil時,會運行通過,且會將roomCount 設定為一個int類型的合理值。然而,如上所述,當residence為空白時,這個代碼將會導致執行階段錯誤。
可選鏈提供了一種另一種獲得numberOfRooms的方法。利用可選鏈,使用問號來代替原來!的位置:
if let roomCount =john.residence?.numberOfRooms { println("John's residence has \(roomCount) room(s).")} else { println("Unable to retrieve the number of rooms.")}// 列印 "Unable toretrieve the number of rooms.
這告訴 Swift 來連結自判斷residence?屬性,如果residence存在則取回numberOfRooms的值。
因為這種嘗試獲得numberOfRooms的操作有可能失敗,可選鏈會返回Int?類型值,或者稱作“自判斷Int”。當residence是空的時候(上例),選擇Int將會為空白,因此會出先無法訪問numberOfRooms的情況。
要注意的是,即使numberOfRooms是非自判斷Int(Int?)時這一點也成立。只要是通過可選鏈的請求就意味著最後numberOfRooms總是返回一個Int?而不是Int。
你可以自己定義一個Residence執行個體給john.residence,這樣它就不再為空白了:
john.residence = Residence()
john.residence 現在有了實際存在的執行個體而不是nil了。如果你想使用和前面一樣的可選鏈來獲得numberOfRoooms,它將返回一個包含預設值 1 的Int?:
if let roomCount =john.residence?.numberOfRooms { println("John's residence has \(roomCount) room(s).")} else { println("Unable to retrieve the number of rooms.")}// 列印 "John'sresidence 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 { return rooms[i] } func printNumberOfRooms() { println("The number of rooms is \(numberOfRooms)") } var address: Address?}
因為Residence儲存了一個Room執行個體的數組,它的numberOfRooms屬性值不是一個固定的儲存值,而是通過計算而來的。numberOfRooms屬性值是由返回rooms數組的count屬性值得到的。
為了能快速存取rooms數組,Residence定義了一個唯讀子指令碼,通過插入數組的元素角標就可以成功調用。如果該角標存在,子指令碼則將該元素返回。
Residence中也提供了一個printNumberOfRooms的方法,即簡單的列印房間個數。
最後,Residence定義了一個自判斷屬性叫address(address?)。Address類的屬性將在後面定義。用於rooms數組的Room類是一個很簡單的類,它只有一個name屬性和一個設定room名的初始化器。
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 { return buildingName } else if buildingNumber { return buildingNumber } else { return nil } }}
Address類還提供了一個buildingIdentifier的方法,它的返回值類型為String?。這個方法檢查buildingName和buildingNumber的屬性,如果buildingName有值則將其返回,或者如果buildingNumber有值則將其返回,再或如果沒有一個屬性有值,返回空。
通過可選鏈調用屬性
正如上面“可選鏈可替代強制解析”中所述,你可以利用可選鏈的可選值擷取屬性,並且檢查屬性是否擷取成功。然而,你不能使用可選鏈為屬性賦值。
使用上述定義的類來建立一個人執行個體,並再次嘗試後去它的numberOfRooms屬性:
let john = Person()if let roomCount = john.residence?.numberOfRooms{ println("John's residence has \(roomCount) room(s).")} else { println("Unable to retrieve the number of rooms.")}// 列印 "Unable toretrieve the number of rooms。
由於john.residence是空,所以這個可選鏈和之前一樣失敗了,但是沒有執行階段錯誤。
通過可選鏈調用方法
你可以使用可選鏈的來調用可選值的方法並檢查方法調用是否成功。即使這個方法沒有返回值,你依然可以使用可選鏈來達成這一目的。
Residence的printNumberOfRooms方法會列印numberOfRooms的當前值。方法如下:
func printNumberOfRooms(){ println(“The number of rooms is \(numberOfRooms)”)}
這個方法沒有返回值。但是,沒有返回值類型的函數和方法有一個隱式的返回值類型Void(參見Function Without Return Values)。
如果你利用可選鏈調用此方法,這個方法的返回值類型將是Void?,而不是Void,因為當通過可選鏈調用方法時返回值總是可選類型(optional type)。,即使是這個方法本是沒有定義返回值,你也可以使用if語句來檢查是否能成功調用printNumberOfRooms方法:如果方法通過可選鏈調用成功,printNumberOfRooms的隱式返回值將會是Void,如果沒有成功,將返回nil:
if john.residence?.printNumberOfRooms() { println("It was possible to print the number of rooms.")} else { println("It was not possible to print the number of rooms.")}// 列印 "It was notpossible to print the number of rooms."。
使用可選鏈調用子指令碼
你可以使用可選鏈來嘗試從子指令碼擷取值並檢查子指令碼的調用是否成功,然而,你不能通過可選鏈來設定子代碼。
注意:當你使用可選鏈來擷取子指令碼的時候,你應該將問號放在子指令碼括弧的前面而不是後面。可選鏈的問號一般直接跟在自判斷表達語句的後面。
下面這個例子用在Residence類中定義的子指令碼來擷取john.residence數組中第一個房間的名字。因為john.residence現在是nil,子指令碼的調用失敗了。
if let firstRoomName =john.residence?[0].name { println("The first room name is \(firstRoomName).")} else { println("Unable to retrieve the first room name.")}// 列印 "Unable toretrieve the first room name."。
在子代碼調用中可選鏈的問號直接跟在john.residence的後面,在子指令碼括弧的前面,因為john.residence是可選鏈試圖獲得的可選值。
如果你建立一個Residence執行個體給john.residence,且在他的rooms數組中有一個或多個Room執行個體,那麼你可以使用可選鏈通過Residence子指令碼來擷取在rooms數組中的執行個體了:
let johnsHouse = Residence()johnsHouse.rooms += Room(name: "LivingRoom")johnsHouse.rooms += Room(name:"Kitchen")john.residence = johnsHouse if let firstRoomName =john.residence?[0].name { println("Thefirst room name is \(firstRoomName).")} else { println("Unable to retrieve the first room name.")}// 列印 "The firstroom name is Living Room."。
串連多層連結
你可以將多層可選鏈串連在一起,可以掘模數型內更下層的屬性方法和子指令碼。然而多層可選鏈不能再添加比已經返回的可選值更多的層。也就是說:
如果你試圖獲得的類型不是可選類型,由於使用了可選鏈它將變成可選類型。如果你試圖獲得的類型已經是可選類型,由於可選鏈它也不會提高自判斷性。
因此:
如果你試圖通過可選鏈獲得Int值,不論使用了多少層連結返回的總是Int?。相似的,如果你試圖通過可選鏈獲得Int?值,不論使用了多少層連結返回的總是Int?。
下面的例子試圖擷取john的residence屬性裡的address的street屬性。這裡使用了兩層可選鏈來聯絡residence和address屬性,他們兩者都是可選類型:
if let johnsStreet = john.residence?.address?.street{ println("John's street name is \(johnsStreet).")} else { println("Unable to retrieve the address.")}// 列印 "Unable toretrieve the address.”。
john.residence的值現在包含一個Residence執行個體,然而john.residence.address現在是nil,因此john.residence?.address?.street調用失敗。
從上面的例子發現,你試圖獲得street屬性值。這個屬性的類型是String?。因此儘管在可選類型屬性前使用了兩層可選鏈,john.residence?.address?.street的返回值類型也是String?。
如果你為Address設定一個執行個體來作為john.residence.address的值,並為address的street屬性設定一個實際值,你可以通過多層可選鏈來得到這個屬性值。
let johnsAddress = Address()johnsAddress.buildingName = "TheLarches"johnsAddress.street = "LaurelStreet"john.residence!.address = johnsAddress if let johnsStreet =john.residence?.address?.street { println("John's street name is \(johnsStreet).")} else { println("Unable to retrieve the address.")}// 列印 "John'sstreet name is Laurel Street."。
值得注意的是,“!”符的在定義address執行個體時的使用(john.residence.address)。john.residence屬性是一個可選類型,因此你需要在它擷取address屬性之前使用!解析以獲得它的實際值。
連結自判斷返回值的方法
前面的例子解釋了如何通過可選鏈來獲得可選類型屬性值。你也可以通過調用返回可選類型值的方法並按需連結方法的返回值。
下面的例子通過可選鏈調用了Address類中的buildingIdentifier 方法。這個方法的返回值類型是String?。如上所述,這個方法在可選鏈調用後最終的返回值類型依然是String?:
if let buildingIdentifier =john.residence?.address?.buildingIdentifier() { println("John's building identifier is\(buildingIdentifier).")}// 列印 "John'sbuilding identifier is The Larches."。
如果你還想進一步對方法返回值執行可選鏈,將可選鏈問號符放在方法括弧的後面:
if let upper =john.residence?.address?.buildingIdentifier()?.uppercaseString { println("John's uppercase building identifier is \(upper).")}// 列印 "John's uppercasebuilding identifier is THE LARCHES."。
注意:在上面的例子中,你將可選鏈問號符放在括弧後面是因為你想要連結的可選值是buildingIdentifier方法的返回值,不是buildingIdentifier方法本身。