★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(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
if residence
存在的值。
由于尝试访问numberOfRooms
可能会失败,因此可选的链接尝试将返回type Int?
或“ 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
and Residence
模型。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."
访问可选类型的下标
如果下标返回的是可选类型的值(例如,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
,其中包含两个将键映射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 does not begin with "The".")
- }
- }
- // Prints "John's building identifier begins with "The"."
注意
在上面的例子中,您将可选链接问号后的括号内,因为你要串联上可选的值是buildingIdentifier()
方法的返回值,而不是buildingIdentifier()
方法本身。