Swift5.4 语言指南(十八) 可选链接
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9739421.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
可选链接是用于查询和调用当前可能是的可选属性,方法和下标的过程nil
。如果可选包含值,则属性,方法或下标调用成功;否则,调用成功。如果可选参数为nil
,则属性,方法或下标调用返回nil
。可以将多个查询链接在一起,如果链中的任何链接为,则整个链都会正常失败nil
。
笔记
Swift中的可选链接类似于nil
Objective-C中的消息传递,但是它适用于任何类型,并且可以检查成功或失败。
可选链接作为强制展开的替代方法
您可以通过在?
要调用其属性,方法或下标的可选值之后放置问号()来指定可选链,如果可选值不是- nil
。这非常类似于将感叹号(!
)放在可选值之后以强制展开其值。主要区别在于,当可选选项为时,可选链接会优雅地失败nil
,而当可选选项为时,强制展开会触发运行时错误nil
。
为了反映可以在nil
值上调用可选链接的事实,即使您要查询的属性,方法或下标返回非可选值,可选链接调用的结果也始终是可选值。您可以使用此可选返回值来检查可选链接调用是否成功(返回的可选值包含一个值),或者由于nil
链中的值而失败(返回的可选值是nil
)。
具体来说,可选链接调用的结果与期望的返回值具有相同的类型,但包装在可选中。通过可选链接访问时,通常返回an的属性Int
将返回a Int?
。
接下来的几个代码片段演示了可选链接与强制展开如何不同,并使您能够检查是否成功。
首先,定义了两个类Person
,Residence
分别定义为和:
- class Person {
- var residence: Residence?
- }
- class Residence {
- var numberOfRooms = 1
- }
Residence
实例有一个Int
名为的属性numberOfRooms
,默认值为1
。Person
实例具有residence
类型的可选属性Residence?
。
如果创建一个新Person
实例,则其residence
属性默认为nil
,因为它是可选的。在下面的代码中,john
其residence
属性值为nil
:
- let john = Person()
如果您尝试访问numberOfRooms
此人的属性residence
,则通过在其后residence
强制放置一个感叹号来对其值进行解包,则会触发运行时错误,因为没有residence
要解包的值:
- let roomCount = john.residence!.numberOfRooms
- // this triggers a runtime error
上面的代码在john.residence
为非nil
值时成功,并将设置roomCount
为Int
包含适当数量房间的值。但是,如上所述,此代码始终会在residence
is时触发运行时错误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
属性上,并检索numberOfRooms
ifresidence
存在的值。
由于尝试访问numberOfRooms
有可能失败,因此可选的链接尝试将返回typeInt?
或“ optional Int
”的值。如上例所示,当residence
为is时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)."
定义可选链接的模型类
您可以将可选链接与深度超过一级的属性,方法和下标一起使用。这使您可以深入研究相互关联类型的复杂模型中的子属性,并检查是否可以访问这些子属性上的属性,方法和下标。
下面的代码段定义了四个模型类,以供随后的几个示例中使用,包括多级可选链接的示例。这些类通过添加和类以及相关的属性,方法和下标,从上面扩展了Person
andResidence
模型。Room
Address
该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
属性只是count
从rooms
数组中返回该属性的值。
作为访问其rooms
数组的捷径,此版本Residence
提供了一个读写下标,该下标提供对rooms
数组中请求的索引处的房间的访问。
此版本的版本Residence
还提供了一种称为的方法printNumberOfRooms
,该方法可以简单地打印住宅中的房间数量。
最后,Residence
定义一个名为的可选属性address
,类型为Address?
。Address
此属性的类类型在下面定义。
在Room
用于类rooms
阵列是简单的类与一种属性调用name
,以及一个初始值设定到该属性设置为一个合适的房间名称:
- class Room {
- let name: String
- init(name: String) { self.name = name }
- }
此模型中的最后一个类称为Address
。此类具有type的三个可选属性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
,如果它有一个值,或buildingNumber
与级联street
如果两者都具有值,或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
在此示例中,设置的address
属性的尝试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()
没有调用该函数,因为没有打印任何内容。
通过可选链接调用方法
您可以使用可选链接来对可选值调用方法,并检查该方法调用是否成功。即使该方法未定义返回值,也可以执行此操作。
在printNumberOfRooms()
对方法Residence
类打印的当前值numberOfRooms
。该方法的外观如下:
- func printNumberOfRooms() {
- print("The number of rooms is \(numberOfRooms)")
- }
此方法未指定返回类型。但是,没有返回类型的函数和方法具有隐式返回类型Void
,如无返回值的函数中所述。这意味着它们返回的值为()
,或者为空的元组。
如果通过可选链对可选值调用此方法,则该方法的返回类型将为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."
如果尝试通过可选链接设置属性,则情况也是如此。上面的“通过可选链接访问属性”中的示例尝试为设置一个address
值john.residence
,即使该residence
属性为nil
。通过可选链接设置属性的任何尝试都会返回type的值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."
通过可选链接访问下标
您可以使用可选链接尝试从下标中检索和设置可选值上的值,并检查该下标调用是否成功。
笔记
通过可选链访问可选值上的下标时,将问号放在下标的括号之前,而不是之后。可选链接问号总是紧接在表达式的可选部分之后。
下面的示例尝试使用在类上定义的下标检索属性rooms
数组中第一个房间的名称。因为当前为,所以下标调用失败:john.residence
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.")
- }
- // Prints "Unable to retrieve the first room name."
此下标调用中的可选链接问号被放置在下john.residence
标括号之前,之后,因为john.residence
这是尝试进行可选链接的可选值。
同样,您可以尝试通过带有可选链接的下标设置新值:
- john.residence?[0] = Room(name: "Bathroom")
此下标设置尝试也失败了,因为residence
当前为nil
。
如果您创建一个实际Residence
实例并将其分配给john.residence
,并Room
在其rooms
数组中包含一个或多个实例,则可以使用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."
访问可选类型的下标
如果下标返回的是可选类型的值(例如,SwiftDictionary
类型的键下标),则在下标的右括号后面放置一个问号,以链接到其可选的返回值上:
- 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
,其中包含两个将键映射String
到Int
值数组的键值对。该示例使用可选链接将"Dave"
数组中的第一项设置为91
;。使"Bev"
数组中的第一项增加1
; 并尝试将数组中的第一项设置为的键"Brian"
。前两个调用成功,因为testScores
字典包含"Dave"
和的键"Bev"
。第三次调用失败,因为testScores
字典不包含的键"Brian"
。
链接多个级别的链接
您可以将多个级别的可选链接链接在一起,以深入挖掘模型中更深层的属性,方法和下标。但是,多个级别的可选链接不会为返回值添加更多级别的可选性。
换一种方式:
- 如果您尝试检索的类型不是可选的,则由于可选的链接,它将变为可选的。
- 如果你正在尝试检索类型是已经可选的,它不会变得更因为链接可选。
所以:
- 如果尝试
Int
通过可选的链接检索值,Int?
则无论使用多少级链接,总是会返回an 。 - 同样,如果您尝试
Int?
通过可选的链接检索值,Int?
则无论使用多少级链接,总是会返回an 。
以下示例尝试访问的street
属性address
的residence
属性john
。有2个可选链接的水平在这里使用,以链通过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?
,即使除了该属性的基础可选类型之外,还应用了两个级别的可选链接,它的返回值也为。
如果您将实际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."
在此示例中,设置的address
属性的尝试john.residence
将成功,因为john.residence
当前的值包含有效Residence
实例。
链接具有可选返回值的方法
前面的示例显示了如何通过可选链接检索可选类型的属性的值。您还可以使用可选链接来调用返回可选类型值的方法,并根据需要链接该方法的返回值。
下面的示例通过可选的链接调用Address
类的buildingIdentifier()
方法。此方法返回type的值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 doesn't begin with \"The\".")
- }
- }
- // Prints "John's building identifier begins with "The"."
笔记
在上面的例子中,您将可选链接问号后的括号内,因为你要串联上可选的值是buildingIdentifier()
方法的返回值,而不是buildingIdentifier()
方法本身。