Swift5.1 语言指南(十八) 可选链接

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9739421.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

可选链接是一个查询和调用当前可选的可选项的属性,方法和下标的过程nil。如果optional包含值,则属性,方法或下标调用成功; 如果是可选的nil,则返回属性,方法或下标调用nil。多个查询可以链接在一起,如果链中的任何链接,整个链都会正常失败nil

注意

Swift中的可选链接类似于nilObjective-C中的消息传递,但其方式适用于任何类型,并且可以检查其成功与否。

可选链接作为强制解包的替代方法

您可以通过?在可选值之后放置一个问号()来指定可选链接,如果可选值为非,则在该值上调用属性,方法或下标nil。这与!在可选值之后放置感叹号()以强制展开其值非常相似。主要区别在于可选链接在可选项时优雅地失败nil,而强制解包在可选项时触发运行时错误nil

要反映可以在nil值上调用可选链接的事实,可选链接调用的结果始终是可选值,即使要查询的属性,方法或下标返回非可选值。您可以使用此可选返回值来检查可选链接调用是否成功(返回的可选项包含值),或者由于nil链中的值(返回的可选值为nil)而未成功。

具体来说,可选链接调用的结果与预期返回值的类型相同,但包含在可选中。通常返回an的属性IntInt?在通过可选链接访问时返回。

接下来的几个代码片段演示了可选链接与强制解包的区别,并使您能够检查是否成功。

首先,调用PersonResidence定义了两个类:

  1. class Person {
  2. var residence: Residence?
  3. }
  4. class Residence {
  5. var numberOfRooms = 1
  6. }

Residence实例有一个Int名为的属性numberOfRooms,默认值为1Person实例具有residence类型的可选属性Residence?

如果您创建一个新Person实例,则其residence属性默认初始化为nil,因为它是可选的。在下面的代码,john具有residence的属性值nil

  1. let john = Person()

如果您尝试访问numberOfRooms此人的属性residence,通过在其后放置感叹号residence强制展开其值,则会触发运行时错误,因为没有residence值要解包:

  1. let roomCount = john.residence!.numberOfRooms
  2. // this triggers a runtime error

如果john.residence具有非nil值,则上面的代码将成功,并将设置roomCountInt包含适当房间数的值。然而,当该代码始终触发运行时错误residencenil,如上述所示。

可选链接提供了一种访问其值的替代方法numberOfRooms。要使用可选链接,请使用问号代替感叹号:

  1. if let roomCount = john.residence?.numberOfRooms {
  2. print("John's residence has \(roomCount) room(s).")
  3. } else {
  4. print("Unable to retrieve the number of rooms.")
  5. }
  6. // Prints "Unable to retrieve the number of rooms."

这告诉Swift在可选residence属性上“链” 并检索numberOfRoomsif residenceexists 的值。

由于访问尝试numberOfRooms可能会失败,因此可选的链接尝试返回值类型Int?或“可选Int”。如果residencenil,如上面的例子中,这个可选的Int也将是nil,反映事实,这是不可能的访问numberOfRooms。可选的Int是通过可选的结合访问以解开的整数并分配非可选值到roomCount变量。

请注意,即使这numberOfRooms是非可选的,也是如此Int。通过可选链查询它的事实意味着调用numberOfRooms将始终返回Int?而不是Int

您可以将Residence实例分配给john.residence,以便它不再具有nil值:

  1. john.residence = Residence()

john.residence现在包含一个实际的Residence实例,而不是nil。如果您尝试使用numberOfRooms与以前相同的可选链接进行访问,它现在将返回Int?包含默认numberOfRooms值的1

  1. if let roomCount = john.residence?.numberOfRooms {
  2. print("John's residence has \(roomCount) room(s).")
  3. } else {
  4. print("Unable to retrieve the number of rooms.")
  5. }
  6. // Prints "John's residence has 1 room(s)."

为可选链接定义模型类

您可以使用可选链接来调用多个级别的属性,方法和下标。这使您可以深入查看相互关联类型的复杂模型中的子属性,并检查是否可以访问这些子属性上的属性,方法和下标。

下面的代码片段定义了四个模型类,用于几个后续示例,包括多级可选链接的示例。这些类在所述展开PersonResidence通过添加模型从上方RoomAddress类,具有相关联的属性,方法,和下标。

Person班以同样的方式前的定义:

  1. class Person {
  2. var residence: Residence?
  3. }

Residence门课比以前更复杂。这一次,Residence该类定义了一个名为的变量属性rooms,该属性使用类型为空的数组进行初始化[Room]

  1. class Residence {
  2. var rooms = [Room]()
  3. var numberOfRooms: Int {
  4. return rooms.count
  5. }
  6. subscript(i: Int) -> Room {
  7. get {
  8. return rooms[i]
  9. }
  10. set {
  11. rooms[i] = newValue
  12. }
  13. }
  14. func printNumberOfRooms() {
  15. print("The number of rooms is \(numberOfRooms)")
  16. }
  17. var address: Address?
  18. }

因为此版本的Residence存储是一个Room实例数组,所以它的numberOfRooms属性实现为计算属性,而不是存储属性。computed numberOfRooms属性只是countrooms数组中返回属性的值。

作为访问其rooms数组的快捷方式,此版本Residence提供了一个读写下标,可以在rooms阵列中的请求索引处提供对房间的访问。

此版本Residence还提供了一种名为的方法printNumberOfRooms,它只是打印住宅中的房间数量。

最后,Residence定义一个名为的可选属性address,其类型为Address?Address此属性的类类型定义如下。

Room用于类rooms阵列是简单的类与一种属性调用name,以及一个初始值设定到该属性设置为一个合适的房间名称:

  1. class Room {
  2. let name: String
  3. init(name: String) { self.name = name }
  4. }

调用此模型中的最后一个类Address。该类有三个可选的类型属性String?。前两个属性buildingNamebuildingNumber,是将特定建筑物识别为地址一部分的替代方法。第三个属性street用于为该地址命名街道:

  1. class Address {
  2. var buildingName: String?
  3. var buildingNumber: String?
  4. var street: String?
  5. func buildingIdentifier() -> String? {
  6. if let buildingNumber = buildingNumber, let street = street {
  7. return "\(buildingNumber) \(street)"
  8. } else if buildingName != nil {
  9. return buildingName
  10. } else {
  11. return nil
  12. }
  13. }
  14. }

所述Address类还提供了一个名为方法buildingIdentifier(),其具有的返回类型String?。此方法检查地址的属性,buildingName如果有值,则返回buildingNumberstreet如果两者都有值,则返回连接,nil否则返回。

通过可选链接访问属性

可选链接作为强制解包的替代方案所示,您可以使用可选链接访问可选值的属性,并检查该属性访问是否成功。

使用上面定义的类来创建新Person实例,并尝试numberOfRooms像以前一样访问其属性:

  1. let john = Person()
  2. if let roomCount = john.residence?.numberOfRooms {
  3. print("John's residence has \(roomCount) room(s).")
  4. } else {
  5. print("Unable to retrieve the number of rooms.")
  6. }
  7. // Prints "Unable to retrieve the number of rooms."

由于john.residencenil,这种可选的链接调用以同样的方式和以前失败。

您还可以尝试通过可选链接设置属性的值:

  1. let someAddress = Address()
  2. someAddress.buildingNumber = "29"
  3. someAddress.street = "Acacia Road"
  4. john.residence?.address = someAddress

在此示例中,设置address属性的尝试john.residence将失败,因为john.residence当前nil

赋值是可选链接的一部分,这意味着不会对=运算符右侧的代码进行求值。在前面的示例中,不容易看到someAddress永远不会评估,因为访问常量没有任何副作用。下面的列表执行相同的分配,但它使用一个函数来创建地址。该函数在返回值之前打印“函数被调用”,这样可以查看是否=评估了操作符的右侧。

  1. func createAddress() -> Address {
  2. print("Function was called.")
  3. let someAddress = Address()
  4. someAddress.buildingNumber = "29"
  5. someAddress.street = "Acacia Road"
  6. return someAddress
  7. }
  8. john.residence?.address = createAddress()

您可以判断该createAddress()函数未被调用,因为没有打印任何内容。

通过可选链接调用方法

您可以使用可选链接在可选值上调用方法,并检查该方法调用是否成功。即使该方法没有定义返回值,也可以执行此操作。

printNumberOfRooms()对方法Residence类打印的当前值numberOfRooms。以下是该方法的外观:

  1. func printNumberOfRooms() {
  2. print("The number of rooms is \(numberOfRooms)")
  3. }

此方法未指定返回类型。但是,没有返回类型的函数和方法具有隐式返回类型Void,如函数无返回值中所述。这意味着它们返回值()或空元组。

如果在带有可选链接的可选值上调用此方法,则方法的返回类型将Void?不是Void,因为通过可选链接调用时返回值始终是可选类型。这使您可以使用if语句来检查是否可以调用该printNumberOfRooms()方法,即使该方法本身不定义返回值。比较来自printNumberOfRooms调用的返回值,nil以查看方法调用是否成功:

  1. if john.residence?.printNumberOfRooms() != nil {
  2. print("It was possible to print the number of rooms.")
  3. } else {
  4. print("It was not possible to print the number of rooms.")
  5. }
  6. // Prints "It was not possible to print the number of rooms."

如果您尝试通过可选链接设置属性,情况也是如此。通过可选链接访问属性中的上述示例尝试为其设置addressjohn.residence,即使该residence属性为nil。任何通过可选链接设置属性的尝试都会返回type值Void?,这使您可以比较nil以查看属性是否已成功设置:

  1. if (john.residence?.address = someAddress) != nil {
  2. print("It was possible to set the address.")
  3. } else {
  4. print("It was not possible to set the address.")
  5. }
  6. // Prints "It was not possible to set the address."

通过可选链接访问下标

您可以使用可选链接尝试从可选值的下标中检索和设置值,并检查该下标调用是否成功。

注意

通过可选链接访问可选值的下标时,将问号放在下标括号之前,而不是之后。可选的链接问号始终紧跟在表达式的可选部分之后。

下面的示例尝试使用类上定义的下标检索属性rooms数组中第一个房间的名称。因为当前,下标调用失败:john.residenceResidencejohn.residencenil

  1. if let firstRoomName = john.residence?[0].name {
  2. print("The first room name is \(firstRoomName).")
  3. } else {
  4. print("Unable to retrieve the first room name.")
  5. }
  6. // Prints "Unable to retrieve the first room name."

此下标调用中的可选链接问号位于下john.residence标括号之后,因为john.residence是可选值,在该值上可选择链接。

同样,您可以尝试通过带有可选链接的下标设置新值:

  1. john.residence?[0] = Room(name: "Bathroom")

此下标设置尝试也失败,因为residence当前nil

如果您创建并分配实际Residence实例john.residence,并Room在其rooms数组中包含一个或多个实例,则可以使用Residence下标rooms通过可选链接访问数组中的实际项:

  1. let johnsHouse = Residence()
  2. johnsHouse.rooms.append(Room(name: "Living Room"))
  3. johnsHouse.rooms.append(Room(name: "Kitchen"))
  4. john.residence = johnsHouse
  5. if let firstRoomName = john.residence?[0].name {
  6. print("The first room name is \(firstRoomName).")
  7. } else {
  8. print("Unable to retrieve the first room name.")
  9. }
  10. // Prints "The first room name is Living Room."

访问可选类型的下标

如果下标返回一个可选类型的值 - 例如Swift Dictionary类型的键下标 - 在下标的右括号后面加一个问号以链接其可选的返回值:

  1. var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
  2. testScores["Dave"]?[0] = 91
  3. testScores["Bev"]?[0] += 1
  4. testScores["Brian"]?[0] = 72
  5. // the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]

上面的示例定义了一个名为的字典testScores,它包含两个将键映射StringInt值数组的键值对。该示例使用可选链接将"Dave"数组中的第一个项目设置为91; 增加"Bev"数组中的第一项1; 并尝试在数组中为键的第一项设置"Brian"。前两个调用成功,因为testScores字典包含"Dave"和的键"Bev"。第三个调用失败,因为testScores字典不包含密钥"Brian"

链接多级链接

您可以将多个级别的可选链接链接在一起,以深入查看模型中更深层次的属性,方法和下标。但是,多级可选链接不会为返回值添加更多级别的可选性。

换一种方式:

  • 如果您要检索的类型不是可选的,则由于可选链接,它将成为可选类型。
  • 如果您尝试检索的类型已经是可选的,则由于链接,它不会变得更加可选。

因此:

  • 如果尝试Int通过可选链接检索值,Int?则无论使用多少级别的链接,都始终返回a。
  • 同样,如果您尝试Int?通过可选链接检索值,Int?则无论使用多少级别的链接,都会始终返回a。

下面的示例尝试访问street属性的address属性的residence属性john。这里有两个级别的可选链接,用于链接residenceaddress属性,两者都是可选类型:

  1. if let johnsStreet = john.residence?.address?.street {
  2. print("John's street name is \(johnsStreet).")
  3. } else {
  4. print("Unable to retrieve the address.")
  5. }
  6. // 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实例设置为for的值john.residence.address,并为地址的street属性设置实际值,则可以street通过多级可选链接访问该属性的值:

  1. let johnsAddress = Address()
  2. johnsAddress.buildingName = "The Larches"
  3. johnsAddress.street = "Laurel Street"
  4. john.residence?.address = johnsAddress
  5. if let johnsStreet = john.residence?.address?.street {
  6. print("John's street name is \(johnsStreet).")
  7. } else {
  8. print("Unable to retrieve the address.")
  9. }
  10. // Prints "John's street name is Laurel Street."

在此示例中,尝试设置address属性john.residence将成功,因为当前值john.residence包含有效Residence实例。

使用可选返回值链接方法

前面的示例演示如何通过可选链接检索可选类型的属性的值。您还可以使用可选链接来调用返回可选类型值的方法,并在需要时链接该方法的返回值。

下面的示例通过可选链接调用Address类的buildingIdentifier()方法。此方法返回type的值String?。如上所述,可选链接后此方法调用的最终返回类型还包括String?

  1. if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
  2. print("John's building identifier is \(buildingIdentifier).")
  3. }
  4. // Prints "John's building identifier is The Larches."

如果你想在这个方法的返回值进行进一步的可选链接,将链接可选问号后,该方法的括号:

  1. if let beginsWithThe =
  2. john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
  3. if beginsWithThe {
  4. print("John's building identifier begins with \"The\".")
  5. } else {
  6. print("John's building identifier does not begin with \"The\".")
  7. }
  8. }
  9. // Prints "John's building identifier begins with "The"."

注意

在上面的例子中,您将可选链接问号的括号内,因为你要串联上可选的值是buildingIdentifier()方法的返回值,而不是buildingIdentifier()方法本身。

转载于:https://www.cnblogs.com/strengthen/p/9739421.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值