十七 可選鏈swift
可選鏈(Optional Chaining)是一種能夠請求和調用屬性、方法及子腳本的過程,它的自判斷性體現於請求或調用的目標當前可能爲空(nil)。若是自判斷的目標有值,那麼調用就會成功;相反,若是選擇的目標爲空(nil),則這種調用將返回空(nil)。屢次請求或調用能夠被連接在一塊兒造成一個鏈,若是任何一個節點爲空(nil)將致使整個鏈失效。數組
可選鏈可替代強制解析app
經過在想調用的屬性、方法、或子腳本的可選值(optional value)(非空)後面放一個問號,能夠定義一個可選鏈。這一點很像在可選值後面放一個聲明符號來強制拆得其封包內的值。他們的主要的區別在於當可選值爲空時可選鏈即刻失敗,然而通常的強制解析將會引起運行時錯誤。ide
爲了反映可選鏈能夠調用空(nil),不論你調用的屬性、方法、子腳本等返回的值是否是可選值,它的返回結果都是一個可選值。你能夠利用這個返回值來檢測你的可選鏈是否調用成功,有返回值即成功,返回nil則失敗。ui
調用可選鏈的返回結果與本來的返回結果具備相同的類型,可是本來的返回結果被包裝成了一個可選值,當可選鏈調用成功時,一個應該返回Int的屬性將會返回Int?。spa
下面幾段代碼將解釋可選鏈和強制解析的不一樣。blog
首先定義兩個類Person和Residence。ip
class Person { var residence: Residence? } class Residence { var numberOfRooms = 1 }
Residence具備一個Int類型的numberOfRooms,其值爲 1。Person具備一個自判斷residence屬性,它的類型是Residence?。it
若是你建立一個新的Person實例,它的residence屬性因爲是被定義爲自判斷型的,此屬性將默認初始化爲空:io
let yu3 = Person()
若是你想使用感嘆號(!)強制解析得到這我的residence屬性numberOfRooms屬性值,將會引起運行時錯誤,由於這時沒有能夠供解析的residence值。
let count = yu3.residence!.numberOfRooms
運行時錯誤
當yu3.residence不是nil時,會運行經過,且會將roomCount 設置爲一個int類型的合理值。然而,如上所述,當residence爲空時,這個代碼將會致使運行時錯誤。
可選鏈提供了一種另外一種得到numberOfRooms的方法。利用可選鏈,使用問號來代替原來!的位置:
if let roomCount = yu3.residence?.numberOfRooms { print("John's residence has \(roomCount) room(s).") } else { print("Unable to retrieve the number of rooms.") }
這告訴 Swift 來連接自判斷residence?屬性,若是residence存在則取回numberOfRooms的值。
由於這種嘗試得到numberOfRooms的操做有可能失敗,可選鏈會返回Int?類型值,或者稱做「自判斷Int」。當residence是空的時候(上例),選擇Int將會爲空,所以會出先沒法訪問numberOfRooms的狀況。
要注意的是,即便numberOfRooms是非自判斷Int(Int?)時這一點也成立。只要是經過可選鏈的請求就意味着最後numberOfRooms老是返回一個Int?而不是Int。
你能夠本身定義一個Residence實例給john.residence,這樣它就再也不爲空了:
yu3.residence = Residence() yu3.residence?.numberOfRooms
爲可選鏈定義模型類
你可使用可選鏈來多層調用屬性,方法,和子腳本。這讓你能夠利用它們之間的複雜模型來獲取更底層的屬性,並檢查是否能夠成功獲取此類底層屬性。
後面的代碼定義了四個將在後面使用的模型類,其中包括多層可選鏈。這些類是由上面的Person和Residence模型經過添加一個Room和一個Address類拓展來。
class Person1 { var residence: Residence1? } class Residence1 { var rooms = [Room]() var numberOfRooms: Int { return rooms.count } subscript(i: Int) -> Room { return rooms[i] } func printNumberOfRooms() { print("The number of rooms is \(numberOfRooms)") } var address: Address? }
由於Residence1存儲了一個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 != nil) { return buildingName } else if (buildingNumber != nil) { return buildingNumber } else { return nil } } }
Address類還提供了一個buildingIdentifier的方法,它的返回值類型爲String?。這個方法檢查buildingName和buildingNumber的屬性,若是buildingName有值則將其返回,或者若是buildingNumber有值則將其返回,再或若是沒有一個屬性有值,返回空。
經過可選鏈調用屬性
正如上面「 可選鏈可替代強制解析」中所述,你能夠利用可選鏈的可選值獲取屬性,而且檢查屬性是否獲取成功。然而,你不能使用可選鏈爲屬性賦值。
使用上述定義的類來建立一我的實例,並再次嘗試後去它的numberOfRooms屬性:
let john = Person1() 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是空,因此這個可選鏈和以前同樣失敗了,可是沒有運行時錯誤。
經過可選鏈調用方法
你可使用可選鏈的來調用可選值的方法並檢查方法調用是否成功。即便這個方法沒有返回值,你依然可使用可選鏈來達成這一目的。
Residence的printNumberOfRooms方法會打印numberOfRooms的當前值。
若是你利用可選鏈調用此方法,這個方法的返回值類型將是Void?,而不是Void,由於當經過可選鏈調用方法時返回值老是可選類型(optional type)。,即便是這個方法本是沒有定義返回值,你也可使用if語句來檢查是否能成功調用printNumberOfRooms方法:若是方法經過可選鏈調用成功,printNumberOfRooms的隱式返回值將會是Void,若是沒有成功,將返回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.") }
使用可選鏈調用子腳本
你可使用可選鏈來嘗試從子腳本獲取值並檢查子腳本的調用是否成功,然而,你不能經過可選鏈來設置子代碼。
下面這個例子用在Residence類中定義的子腳原本獲取john.residence數組中第一個房間的名字。由於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是可選鏈試圖得到的可選值。
若是你建立一個Residence實例給john.residence,且在他的rooms數組中有一個或多個Room實例,那麼你可使用可選鏈經過Residence子腳原本獲取在rooms數組中的實例了:
let johnHouse = Residence1() johnHouse.rooms.append(Room(name: "臥室")) johnHouse.rooms.append(Room(name: "客廳")) johnHouse.rooms.append(Room(name: "廚房")) john.residence = johnHouse if let firstRoomName = john.residence?[0].name { print("The first room name is \(firstRoomName).") } else { print("Unable to retrieve the first room name.") }
鏈接多層連接
你能夠將多層可選鏈鏈接在一塊兒,能夠掘取模型內更下層的屬性方法和子腳本。然而多層可選鏈不能再添加比已經返回的可選值更多的層。 也就是說:
若是你試圖得到的類型不是可選類型,因爲使用了可選鏈它將變成可選類型。 若是你試圖得到的類型已是可選類型,可選鏈它也不會提升自判斷性。
所以:
若是你試圖經過可選鏈得到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?。
若是你爲Address設定一個實例來做爲john.residence.address的值,併爲address的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實例時的使用(john.residence.address)。john.residence屬性是一個可選類型,所以你須要在它獲取address屬性以前使用!解析以得到它的實際值。
連接自判斷返回值的方法
前面的例子解釋瞭如何經過可選鏈來得到可選類型屬性值。你也能夠經過調用返回可選類型值的方法並按需連接方法的返回值.
下面的例子經過可選鏈調用了Address類中的buildingIdentifier 方法。這個方法的返回值類型是String?。如上所述,這個方法在可選鏈調用後最終的返回值類型依然是String?:
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() { print("John's building identifier is \(buildingIdentifier).") }
若是你還想進一步對方法返回值執行可選鏈,將可選鏈問號符放在方法括號的後面:
if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString { print("John's uppercase building identifier is \(upper).") }
注意: 在上面的例子中,你將可選鏈問號符放在括號後面是由於你想要連接的可選值是buildingIdentifier方法的返回值,不是buildingIdentifier方法自己。