官方文檔鏈接
原文鏈接
可選鏈(Optional Chaining)是為了在一個可能當前值為nil
的optional
類型里固该,查詢和調(diào)用屬性眠蚂,方法和下標腳本的一個過程骏掀。如果這個可選類型包含了一個值宠纯,屬性卸夕,方法或是下標腳本,那么就會調(diào)用成功婆瓜;如果這個可選類型為nil
快集,那么屬性,方法或下表腳本調(diào)用返回值就為nil
廉白。多個連續(xù)的調(diào)用可以被鏈接在一起个初,但是如果在這個鏈接里有的節(jié)點為nil
,那么就會導(dǎo)致整個鏈接失敗猴蹂。
注意:
在Swift中院溺,可選鏈和Objective-C中消息為`nil`有些類似,但是Swift可以使用在任何類型中晕讲,并且可以檢查調(diào)用是否成功。
使用可選鏈調(diào)用來強制展開
你可以在你希望調(diào)用的屬性马澈,方法或者下標腳本后面瓢省,如果這些值為非nil
,那么你可以在可選值的后面使用一個問號(?)來替代可選鏈。這和在可選值后面放一個感嘆號(?)痊班,強制解包有些類似勤婚。主要的不同就是可選鏈會在可選值為nil
的調(diào)用失敗,因為強制解包會在可選值為nil
的時候觸發(fā)運行時錯誤涤伐。
為了反應(yīng)可選鏈可以被一個nil
值調(diào)用馒胆,可選鏈調(diào)用的結(jié)果總是可選值,不論這個屬性凝果,方法或下標腳本返回的是不是非可選值祝迂。你可以使用這個可選返回值來檢查可選鏈調(diào)用成功(返回的可選變量包含一個值),或者由于在鏈接里有一個nil
值就會調(diào)用失敗器净。
特別地型雳,可選鏈地調(diào)用的結(jié)果與原本煩人返回結(jié)果有相同的類型,但是包裝成了一個可選類型山害。當通過可選鏈的方式纠俭,一個Int
型的屬性會返回一個Int?
。
下面的代碼片段解釋了可選鏈調(diào)用和強制展開的不同浪慌。
首先冤荆,聲明了兩個類,分別為Person
和Residence
:
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
Residence
市實例有一個名為numberOfRooms
的Int
類型的屬性权纤,有一個默認值1钓简。Person
實例有一個可選的residence
類型Residence?
乌妒。
如果你創(chuàng)建了一個新的Person
實例,它的residence
屬性默認初始化為nil
涌庭,在下面的代碼芥被,john
有一個residence
屬性為nil
。
let john = Person()
如果你想訪問這個person
的residence
的numberOfRooms
屬性坐榆,可以在residence
后面加一個感嘆號來強制解包它的值拴魄,那么你就會觸發(fā)一個運行時錯誤,因為沒有可展開的residence
席镀。
let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error
上面的代碼如果改成john.residence
有一個非nil
值就會調(diào)用成功匹中。并且設(shè)置一個Int
型的roomCount
存儲房間的數(shù)量。但是豪诲,當residence
為空的時候上面這段代碼會觸發(fā)運行時錯誤顶捷。
可選鏈調(diào)用提供了一種到另一種訪問numberOfRooms
的方法,使用問號(屎篱?)來代替原來嘆號(7辍)的位置:
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
不為nil
了:
john.residence = Residence()
現(xiàn)在就可以正常訪問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)."
為可選鏈定義模型類
通過使用可選鏈可以調(diào)用多層屬性,方法亥啦,和下標腳本炭剪。這樣可以通過各種模型向下訪問各種子屬性。并且判斷能否訪問子屬性的屬性翔脱,方法或下標奴拦。
下面這段代碼定義了四個模型類,這些例子包括多層可空鏈式調(diào)用届吁。為了方便說明错妖,在Person
和Residence
的基礎(chǔ)上增加了Room
和Address
绿鸣,以及相關(guān)的屬性,方法以及下標暂氯。
class Person {
var residence: Residence?
}
Residence
類也比之前更完整潮模。這次,Residence
類定義一個名為rooms
的變量屬性痴施,初始化為[Room]類型的空數(shù)組擎厢。
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?
}
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?
}
現(xiàn)在Residence
有了一個存儲Room
類型的數(shù)組,numberOfRooms
屬性需要計算辣吃,而不是作為單純的變量动遭。計算后的numberOfRooms
返回rooms
數(shù)組的count
屬性值。現(xiàn)在的Residence
還提供訪問rooms
數(shù)組的快捷方式神得, 通過可讀寫的下標來訪問指定位置的數(shù)組元素厘惦。此外,還提供printNumberOfRooms
方法哩簿,這個方法的作用就是輸出這個房子中房間的數(shù)量宵蕉。最后,Residence
定義了一個可空屬性address
节榜,其類型為Address?
羡玛。Address
類的定義在下面會說明。
Room
類被用在rooms
數(shù)組里全跨,它是一個簡單類有一個名為name
的屬性缝左,并且有一個初始化器來設(shè)置房間的名字
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 && street != nil {
return "\(buildingNumber) \(street)"
} else {
return nil
}
}
}
類Address
提供buildingIdentifier()
方法挪钓,返回值為String?
。 如果buildingName
不為空則返回buildingName
耳舅, 如果buildingNumber
不為空則返回buildingNumber
碌上。如果這兩個屬性都為空則返回nil
。
通過可選鏈訪問屬性
正如上文使用可選鏈來強制展開中所述浦徊,可以通過可空鏈式調(diào)用訪問屬性的可空值馏予,并且判斷訪問是否成功。
上面使用類定義來創(chuàng)建一個新的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
霞丧,所以毫無疑問這個可空鏈式調(diào)用失敗。
通過可空鏈式調(diào)用來設(shè)定屬性值:
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress
在這個例子中冕香,通過john.residence
來設(shè)定address
屬性也是不行的蛹尝,因為john.residence
為nil
后豫。
func createAddress() -> Address {
print("Function was called.")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
john.residence?.address = createAddress()
你可以使用createAddress()
函數(shù),就就會知道它沒有被調(diào)用突那,因為什么都沒有打印挫酿。
通過可選鏈來調(diào)用方法
可以通過可空鏈式調(diào)用來調(diào)用方法,并判斷是否調(diào)用成功愕难,即使這個方法沒有返回值早龟。
Residence
中的printNumberOfRooms()
方法輸出當前的numberOfRooms
值:
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
這個方法沒有返回值。但是沒有返回值的方法隱式返回Void
類型务漩,如無返回值函數(shù)中所述拄衰。這意味著沒有返回值的方法也會返回()或者空的元組。
如果在可空值上通過可空鏈式調(diào)用來調(diào)用這個方法饵骨,這個方法的返回類型為Void?
翘悉,而不是Void
,因為通過可空鏈式調(diào)用得到的返回值都是可空的居触。這樣我們就可以使用if
語句來判斷能否成功調(diào)用printNumberOfRooms()
方法妖混,即使方法本身沒有定義返回值。通過返回值是否為nil
可以判斷調(diào)用是否成功:
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."
同樣的轮洋,可以判斷通過可空鏈式調(diào)用來給屬性賦值是否成功制市。在上面的例子中,我們嘗試給john.residence
中的address
屬性賦值弊予,即使residence
為nil
祥楣。通過可空鏈式調(diào)用給屬性賦值會返回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."
通過可選鏈來訪問下標腳本
通過可空鏈式調(diào)用汉柒,我們可以用下標來對可空值進行讀取或?qū)懭胛笸剩⑶遗袛嘞聵苏{(diào)用是否成功。
注意:
當通過可空鏈式調(diào)用訪問可空值的下標的時候碾褂,應(yīng)該將問號放在下標方括號的前面而不是后面兽间。可空鏈式調(diào)用的問號一般直接跟在可空表達式的后面正塌。
下面這個例子用下標訪問john.residence
中rooms
數(shù)組中第一個房間的名稱嘀略,因為john.residence
為nil
,所以下標調(diào)用毫無疑問失敗了:
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
是可空值鸠天。
類似的讼育,可以通過下標,用可空鏈式調(diào)用來賦值:
john.residence?[0] = Room(name: "Bathroom")
這次賦值同樣會失敗,因為residence
目前是nil
窥淆。
如果你創(chuàng)建一個Residence
實例卖宠,添加一些Room
實例并賦值給john.residence
,那就可以通過可選鏈和下標來訪問數(shù)組中的元素:
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
數(shù)組忧饭,包含了兩個鍵值對扛伍, 把String
類型的key映射到一個整形數(shù)組。這個例子用可空鏈式調(diào)用把“Dave”
數(shù)組中第一個元素設(shè)為91
词裤,把”Bev”
數(shù)組的第一個元素+1
刺洒,然后嘗試把”Brian”
數(shù)組中的第一個元素設(shè)為72
。前兩個調(diào)用是成功的吼砂,因為這兩個key存在逆航。但是key“Brian”
在字典中不存在,所以第三個調(diào)用失敗渔肩。
多層鏈接
可以通過多個鏈接多個可空鏈式調(diào)用來向下訪問屬性因俐,方法以及下標。但是多層可空鏈式調(diào)用不會添加返回值的可空性周偎。
也就是說:
如果你訪問的值不是可空的抹剩,通過可空鏈式調(diào)用將會放回可空值。
如果你訪問的值已經(jīng)是可空的蓉坎,通過可空鏈式調(diào)用不會變得“更”可空澳眷。
因此:
通過可空鏈式調(diào)用訪問一個Int
值,將會返回Int?
蛉艾,不過進行了多少次可空鏈式調(diào)用钳踊。
類似的,通過可空鏈式調(diào)用訪問Int?
值勿侯,并不會變得更加可空拓瞪。
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?
矢空,即使已經(jīng)進行了兩次可空的鏈式調(diào)用。
如果把john.residence.address
指向一個實例禀横,并且為address
中的street
屬性賦值屁药,我們就能過通過可空鏈式調(diào)用來訪問street屬性
如果你設(shè)置一個Address
實例作為john.residence.address
,并且為地址的street
屬性設(shè)置一個實際的值柏锄,你可以通過多層鏈接訪問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
是一個可用的Residence
實例复亏,所以對john.residence
的address
屬性賦值成功。
對返回可空值的函數(shù)進行鏈接
上面的例子說明了如何通過可空鏈式調(diào)用來獲取可空屬性值缭嫡。我們還可以通過可空鏈式調(diào)用來調(diào)用返回可空值的方法缔御,并且可以繼續(xù)對可空值進行鏈接。
在下面的例子中妇蛀,通過可空鏈式調(diào)用來調(diào)用Address
的buildingIdentifier()
方法耕突。這個方法返回String?
類型。正如上面所說评架,通過可空鏈式調(diào)用的方法的最終返回值還是String?
:
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
print("John's building identifier is \(buildingIdentifier).")
}
// Prints "John's building identifier is The Larches."
如果要進一步對方法的返回值進行可空鏈式調(diào)用眷茁,在方法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()的返回值是可空值纵诞,而不是方法本身是可空的上祈。