標籤:
詳情轉自:http://wiki.jikexueyuan.com/project/swift/chapter2/07_Closures.html
可空鏈式調用(Optional Chaining)是一種可以請求和調用屬性、方法及下標的過程,它的可空性體現於請求或調用的目標當前可能為空白(nil)。如果可空的目標有值,那麼調用就會成功;如果選擇的目標為空白(nil),那麼這種調用將返回空(nil)。多個連續的調用可以被連結在一起形成一個調用鏈,如果其中任何一個節點為空白(nil)將導致整個鏈調用失敗。
注意: Swift 的可空鏈式調用和 Objective-C 中的訊息為空白有些相像,但是 Swift 可以使用在任意類型中,並且能夠檢查調用是否成功。
使用可空鏈式調用來強制展開
通過在想調用非空的屬性、方法、或下標的可空值(optional value)後面放一個問號,可以定義一個可空鏈。這一點很像在可空值後面放一個歎號(!)來強制展開其中值。它們的主要的區別在於當可空值為空白時可空鏈式只是調用失敗,然而強制展開將會觸發執行階段錯誤。
為了反映可空鏈式調用可以在Null 物件(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
。
通過可空鏈式調用訪問屬性
正如使用可空鏈式調用來強制展開中所述,可以通過可空鏈式調用訪問屬性的可空值,並且判斷訪問是否成功。
下面的代碼建立了一個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
類型,如無傳回值函數中所述。這意味著沒有傳回值的方法也會返回()或者空的元組。
如果在可空值上通過可空鏈式調用來調用這個方法,這個方法的傳回型別為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 = johnsHouseif 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] = 91testScores["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 = johnsAddressif 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()
的傳回值是可空值,而不是方法本身是可空的。
詳情轉自:http://wiki.jikexueyuan.com/project/swift/chapter2/07_Closures.html
進擊的雨燕-------------可空鏈式調用