繼續學習Swift文檔,從上一章節:析構函數,咱們學習了Swift析構函數相關的內容。如今,咱們學習Swift可選連接的相關的內容。因爲篇幅較長,這裏分篇來記錄,接下來,Fighting!html
若是你已經熟悉這部份內容,移步下一章節:錯誤處理swift
可選連接是一個查詢和調用當前可能爲零的可選屬性,方法和下標的過程。 若是可選包含值,則屬性,方法或下標調用成功。 若是可選值爲nil,則屬性,方法或下標調用將返回nil。 能夠將多個查詢連接在一塊兒,若是鏈中的任何連接爲nil,則整個鏈都會失敗。數組
注意
Swift中的可選連接相似於Objective-C中的無消息傳遞,可是它適用於任何類型,而且能夠檢查成功或失敗。bash
若是可選值不爲nil,能夠經過在要調用屬性,方法或下標的可選值以後放置問號(?)來指定可選鏈。這很是相似於將感嘆號(!)放在可選值以後以強制展開其值。主要區別在於,當可選值爲nil時,強制展開會觸發運行時錯誤。app
爲了反映能夠在nil值上調用可選連接的事實,即便要查詢的屬性,方法或下標返回的是非可選值,可選連接調用的結果也始終是可選值。您可使用此可選返回值來檢查可選連接調用是否成功(返回的可選包含一個值),或者因爲鏈中的值爲nil而失敗(返回的可選值爲nil)。ide
具體來講,可選連接調用的結果與預期返回值的類型相同,但包裝在可選中。一般經過可選鏈訪問時,返回Int的屬性將返回Int?。函數
接下來的幾個代碼片斷演示了可選連接與強制展開如何不一樣,並使您可以檢查是否成功。post
首先,定義了兩個類,分別是Person和Residence:學習
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
複製代碼
Residence實例具備一個名爲numberOfRooms的Int屬性,默認值爲1。Person實例具備類型爲Residence?的可選Residence屬性。ui
若是建立新的Person實例,則其residence屬性因爲是可選屬性而默認初始化爲nil。 在下面的代碼中,john的住宅屬性值爲nil:
let john = Person()
複製代碼
若是您嘗試訪問此人的residence的numberOfRooms屬性,方法是在residence後放置一個感嘆號以使其值解開,從而觸發運行時錯誤,由於沒有要解開的residence值:
let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error
複製代碼
上面的代碼在john.residence具備非nil值時成功,並將roomCount設置爲包含適當房間數的Int值。 可是,如上所示,此代碼老是在駐留時間爲nil時觸發運行時錯誤。
可選連接提供了一種訪問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."
複製代碼
這告訴Swift能夠在可選的Residence屬性上「連接」,並在存在residence的狀況下檢索numberOfRooms的值。
因爲嘗試訪問numberOfRooms可能會失敗,所以可選的連接嘗試將返回Int?類型的值,即「 optional Int」。 如上例所示,當Residence爲nil時,此可選的Int也將爲nil,以反映沒法訪問numberOfRooms的事實。 經過可選的綁定訪問可選的Int能夠解開整數並將非可選值分配給roomCount變量。
請注意,即便numberOfRooms是非可選的Int,也是如此。 經過可選鏈查詢它的事實意味着對numberOfRooms的調用將始終返回Int? 而不是Int。
您能夠將一個Residence實例分配給john.residence,以使其再也不具備nil值:
john.residence = Residence()
複製代碼
john.residence如今包含實際的Residence實例,而不是nil。 若是您嘗試使用與之前相同的可選連接來訪問numberOfRooms,如今它將返回一個Int? 包含默認的numberOfRooms值1:
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)."
複製代碼
您能夠將可選連接與深度超過一級的屬性,方法和下標一塊兒使用。 這使您能夠深刻研究相互關聯類型的複雜模型中的子屬性,並檢查是否能夠訪問這些子屬性上的屬性,方法和下標。
下面的代碼段定義了四個模型類,可用於後續的幾個示例中,包括多級可選連接的示例。 這些類經過添加帶有相關屬性,方法和下標的Room和Address類,從上面擴展了Person and Residence模型。
Person類的定義與之前相同:
class Person {
var residence: Residence?
}
複製代碼
Residence類比之前更復雜。 此次,Residence類定義了一個名爲Rooms的變量屬性,該屬性使用[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提供了一個可讀寫的下標,該下標提供了對房間數組中請求的索引的訪問。
此版本的Residence還提供了一種名爲printNumberOfRooms的方法,該方法僅打印該房間的房間數量。
最後,Residence定義了一個名爲address的可選屬性,其類型爲Address?。 此屬性的Address類類型在下面定義。
用於rooms數組的Room類是一個簡單的類,具備一個名爲name的屬性,以及一個用於將該屬性設置爲合適的房間名稱的初始化方法:
class Room {
let name: String
init(name: String) { self.name = name }
}
複製代碼
此模型中的最後一類稱爲地址。 此類具備String?類型的三個可選屬性。 前兩個屬性buildingName和buildingNumber是將特定建築物標識爲地址一部分的替代方法。 第三個屬性street用來爲該地址命名街道:
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if let buildingNumber = buildingNumber, let street = street {
return "\(buildingNumber) \(street)"
} else if buildingName != nil {
return buildingName
} else {
return nil
}
}
}
複製代碼
Address類還提供了一個名爲buildingIdentifier()的方法,該方法的返回類型爲String?。 此方法檢查地址的屬性,若是有值則返回buildingName,若是有值則返回與street串聯的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的地址屬性將失敗,由於john.residence當前爲nil。
賦值是可選連接的一部分,這意味着=運算符右側的任何代碼都不會被調用。 在上一個示例中,不容易發現someAddress從未被調用,由於訪問常量沒有任何反應。 下面的清單執行相同的分配,可是它使用一個函數來建立地址。 該函數在返回值以前打印「調用了函數」,這使您能夠查看=運算符的右側是否已被求值。
func createAddress() -> Address {
print("Function was called.")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
john.residence?.address = createAddress()
複製代碼
您能夠說未調用createAddress()函數,由於未打印任何內容。
您可使用可選連接來對可選值調用方法,並檢查該方法調用是否成功。 即便該方法未定義返回值,也能夠執行此操做。
Residence類上的printNumberOfRooms()方法將打印numberOfRooms的當前值。 該方法以下:
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
複製代碼
此方法未指定返回類型。 可是,沒有返回類型的函數和方法具備隱式的Void返回類型,如 Functions Without Return Values中所述。 這意味着它們返回值()或一個空的元組。
若是您經過可選連接對可選值調用此方法,則該方法的返回類型將爲Void ?,而不是Void,由於經過可選連接調用時,返回值始終爲可選類型。 這使您可使用if語句來檢查是否有可能調用printNumberOfRooms()方法,即便該方法自己並未定義返回值。 將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設置地址值,即便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."
複製代碼
您可使用可選連接嘗試從下標中檢索和設置可選值的值,並檢查該下標調用是否成功。
注意
經過可選連接訪問下標上的下標值時,將問號放在下標方括號以前,而不是以後。 可選連接問號老是緊接在表達式的可選部分以後。
下面的示例嘗試使用Residence類定義的下標檢索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")
複製代碼
該下標設置嘗試也將失敗,由於當前居住地爲nil。
若是您建立一個實際的Residence實例並將其分配給john.residence,而且在rooms數組中包含一個或多個Room實例,則可使用Residence下標經過可選的連接訪問Rooms數組中的實際項目:
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "Living Room"))
johnsHouse.rooms.append(Room(name: "Kitchen"))
john.residence = johnsHouse
if 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類型的關鍵下標),則在下標的右括號後面放置一個問號,以連接到其可選的返回值上:
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
複製代碼
上面的示例定義了一個名爲testScores的字典,該字典包含兩個將字符串鍵映射到Int值數組的鍵值對。 該示例使用可選的連接將「 Dave」數組中的第一項設置爲91。 將「 Bev」數組中的第一項增長1; 並嘗試爲「 Brian」鍵設置數組中的第一項。 前兩個調用成功,由於testScores詞典包含「 Dave」和「 Bev」的鍵。 第三次調用失敗,由於testScores詞典不包含「 Brian」的鍵。
您能夠將多個級別的可選連接連接在一塊兒,以深刻挖掘模型中更深的屬性,方法和下標。 可是,多個級別的可選連接不會爲返回的值添加更多級別的可選性。
換一種方式:
所以:
下面的示例嘗試訪問john的Residence屬性的address屬性的street屬性。 此處使用兩個級別的可選連接,以連接駐留和地址屬性,二者均爲可選類型:
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?。
若是您將實際的Address實例設置爲john.residence.address的值,併爲該地址的street屬性設置了實際值,則能夠經過多層可選鏈訪問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.")
}
// Prints "John's street name is Laurel Street."
複製代碼
在此示例中,設置john.residence的address屬性的嘗試將成功,由於john.residence的值當前包含有效的Residence實例。
前面的示例顯示瞭如何經過可選連接檢索可選類型的屬性的值。 您還可使用可選連接來調用返回可選類型值的方法,並根據須要連接該方法的返回值。
下面的示例經過可選的連接調用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."
複製代碼
若是要對該方法的返回值執行進一步的可選連接,請在該方法的括號後放置可選連接問號:
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()方法的返回值,而不是buildingIdentifier()方法自己。
這章內容主要講了可選連接的做用和用法,涉及到「?」和「!」這兩個關鍵符號,經過使用?,來判斷是否有值或者爲nil,使用可選綁定if-let配合來判斷調用哪一個邏輯。使用!強制解包必定有值的屬性,若爲nil,會報錯;這部份內容理解就好。
喜歡的朋友麻煩動動你的🤚,點個👍哦,謝謝~