第16章:可选链接

作者: 行知路 | 来源:发表于2019-02-20 17:21 被阅读1次

      可选链接是通过可选类型访问属性、方法、下标的过程。如果可选类型是nil则访问失败,如果有真实的值访问成功。多个访问可以链接在一起,如果其中一个访问失败,则所有的代表着访问失败,并返回nil。

    注意
    在可选链接上调用方法、属性、下标时,无论属性是否是可选的、方法是否有返回值,都会自动成为可选的类型。

    16.1 可选链接与强制解包

      通过在可选类型后面添加感叹号的方式强制解包,在可选类型是nil的情况下,则会触发运行时错误,因为没有值要解包。

    class Person {
        var residence: Residence?
    }
    
    class Residence {
        var numberOfRooms = 1
    }
    
    let john = Person()
    
    // 由于可选类型没有值,触发运行时错误
    let roomCount = john.residence!.numberOfRooms
    

      在以上示例中把john.residence!.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."
    

      在以上示例代码中if let roomCount = john.residence?.numberOfRooms通过可选链接来进行访问numberOfRooms属性。由于此时john.residence为空,所以后面的属性访问不会进行同时把nil赋值给roomCount,从而实现if判断。
      可以通过以下代码来实现正常的可选链接访问。

    john.residence = Residence()
    
    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)."
    

    16.2 可选链接访问实例

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

    
    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?
    }
    
    class Room {
        let name: String
        init(name: String) { self.name = name }
    }
    
    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
            }
        }
    }
    

    16.3 可选链接访问属性

      使用上面定义的类来创建新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()函数未被调用,因为没有打印任何内容。

    16.4 可选链接访问方法

      你可以使用可选链接在可选值上调用方法,并检查该方法调用是否成功。即使该方法没有定义返回值,也可以执行此操作。
      在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."
    
    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."
    

    16.5 可选链接访问下标

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

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

      下面的示例尝试使用类上定义的下标检索属性rooms数组中第一个房间的名称。

    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?[0] = Room(name: "Bathroom")
    

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

    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"。

    16.6 多级可选链

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

    换一种方式:

    • 如果您要检索的类型不是可选的,则由于可选链接,它将成为可选类型。
    • 如果您尝试检索的类型已经是可选的,则由于链接,它不会变得更加可选。
      因此:
    • 如果尝试在可选链接同访问一个Int属性的值,那么总是会返回Int?,不管链接具有多少级别。
    • 同样,如果您尝试通过可选链接检索值Int?,会始终返回Init?,无论使用多少级别的链接。
        下面的示例尝试访问street属性的address属性的residence属性john。这里有两个级别的可选链接,用于链接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实例设置为for的值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实例。

    16.6 使用可选返回值链接方法

      前面的示例演示如何通过可选链接检索可选类型的属性的值。你还可以使用可选链接来调用返回可选类型值的方法,并在需要时链接该方法的返回值。
      下面的示例通过可选链接调用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()方法本身。

    相关文章

      网友评论

        本文标题:第16章:可选链接

        本文链接:https://www.haomeiwen.com/subject/niihyqtx.html