Swift-可空链式调用(Optional Chaining)(十五)

Posted 人散风中

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift-可空链式调用(Optional Chaining)(十五)相关的知识,希望对你有一定的参考价值。

前言

其实可空链式调用并没有它的名字那么陌生,简而言之就是对于可选类型Optional(使用问号 ? 后缀表示)和强制展开类型(使用感叹号 ! 后缀表示)的使用方法。在平常写代码的时候只是大概的清楚哪些值是可空的,哪些值是肯定存在的,但是并没有深究可空的调用有何优点,有何使用时需要注意的事项。至少前面写不少示例代码的时候,我也是大都按照自己的想法去定义的。这一小节就是对可空调用的详细描述,至于链式,就是多层调用,大概就是school.classroom.student.name这样的调用方法。

小节知识点,看着挺多挺长,但是如果把可空链式调用这几个忽略掉,就会发现都是我们已经非常熟悉的基本用法:

  • 使用可空链式调用来强制展开
  • 使用可空链式调用定义模型类
  • 通过可空链式调用访问属性
  • 通过可空链式调用来调用方法
  • 通过可控调用方法来访问下标
  • 多层链接
  • 对返回可空值得函数进行链接

可空链式调用是一种可以请求和调用属性、方法以及下标的过程,它的可空性体现在请求和调用的目前当前可能为空nil。如果可空的目标有值,那么就会调用成功;如果选择的目标为空,那么这种调用将会返回空nil。多个连续的调用可以被链接到一起形成一个调用链,如果其中任何一个节点为空nil,将会导致整个调用失败。

Swift的可空链式调用和Objective-C中的消息为空有点像,但是Swift可以使用在任何类型中,并且能够检查调用是否成功。一个可空调用相当于Objective-C中if (message == nil) else ,只是Objective-C中只能用在对象或者变量中,远没有Swift中功能强大。

分条详述

  1. 使用可空链式调用来强制展开

    下面这几句话读起来有点拗口,多理解就好:通过在想调用非空的属性、方法、或下标的可空值optional value后面放置一个问号 ? ,可以定义一个可空链。这一点很像在一个可空值后面放一个感叹号 ! 来强制展开其中值。它们的主要区别在于当可空值为空时可空链式只是调用失败,然而强制揭开将会出发运行错误。

    简单来说,就是当调用一个属性或方法时,我们希望它是非空的,是有值的,但是这个我们并不能确定有值,就会用到可空链。譬如获取网络数据,然后展示,我们当然希望是获取到数据的,但是有时候因为网络、后台的问题,数据为空,此时如果用可空链来处理,就会简单些。

    需要强调的是,可空链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可空类型值。例如,当可空链式调用成功时,一个int类型的结果将会返回int?类型。

    下面几行示例代码展示可空链式调用和强制展开的一些不同点:

    //  户主信息
    class Person 
    var residence: Residence?
    
    //  房子信息,一共有多少房间
    class Residence 
    var numberOfRooms = 1     //  默认只有一个房间
    
    //  创建一个新的 Person 实例
    let personOne = Person()
    //  由于 residence 属性为空,所以下面如果将 ? 换成 ! 强制解开,会报错。另外,这里代码补全就是带 ? 的。
    print(personOne.residence?.numberOfRooms)     //  输出: nil
    //  初始化 residence 属性
    personOne.residence = Residence()
    //  此时 residence 已经有了初始值,但是胆码补全的时候还是用了 ? ,此时如果将 ? 改为 ! , 并不会报错,因为是对一个已经确定的非空值强制展开
    print(personOne.residence!.numberOfRooms)      //  输出: 1
    print(personOne.residence?.numberOfRooms)      //  输出: Optional(1)  ,  表示可空的

    其实可空链式调用最常用的一个场景就是if语句的判断,当可空链式调用返回是nil时执行操作A,不是nil时执行操作B,如下:

    //  可空链式调用用于 if 语句判断
    if let person = personOne.residence?.numberOfRooms 
    //  此时不为空  personOne.residence?.numberOfRooms = 1
     else 
    //  此时为空   personOne.residence?.numberOfRooms = nil
    
  2. 通过可空链式调用定义模型类

    通过使用可空链式调用可以调用多层属性、方法和下标。这样可以通过各种模型向下访问各种子属性。并且可以根据是否为空判断是否可以访问子属性的属性、方法或下标。

    下面示例代码定义了四个模型类,这些例子包括多层可空链式调用。同时,下面几个知识点也会以这4个类为基础说明:

    //  户主信息
    class Person 
    //  房子信息,可能没有房子,所以可选比较合适
    var residence: Residence?
    
    //  房子信息
    class Residence 
    //  房间数组
    var rooms = [Room]()
    //  一共有多少房间
    var numberOfRooms: Int 
        return rooms.count
    
    //  房子地址
    var address: Address?
    //  下标方法,选择第几间房
    subscript(index: Int) -> Room 
        get 
            return rooms[index]     //  取值
        
        set 
            rooms[index] = newValue    //  设置值
        
    
    //  打印房间数量
    func printNumberOfRooms() 
        print("number of rooms : \\(rooms.count)")
    
    
    //  房间信息
    class Room 
    //  房间名字
    let name: String
    init(roomName name: String) 
        self.name = name
    
    
    //  地址信息
    class Address 
    var buildingName: String?  //  建筑名字
    var buildingNumber: String?     //  建筑编号
    var buildingStreet: String?      //  建筑所在街道名称
    //  方法,获取建筑物的唯一标示符,因为可能为空,所以返回的String类型是可选的
    func buildingIdentifier() -> String? 
        if let name = buildingName 
            return name
         else if let number = buildingNumber 
            return number
         else 
            return nil
        
    
    
  3. 通过可空链式调用访问属性

    就是我们平常调用属性、方法的那一套,先创建一个实例,然后去调用。其实,很多时候实例、属性等是可空的还是强制展开的,系统都会帮我们去辨别的,我们需要去理解为什么会出现补全的情况,有时候需要我们手动去决定可选还是强制展开。

    下面是代码示例,主要注意点都写在注释里了:

    //  创建一个户主实例
    let john = Person()
    if let roomCount = john.residence?.numberOfRooms 
    //  可空链式调用成功,这里不会执行,因为 john.residence?.numberOfRooms 此时是 nil
     else 
    //  可空链式调用失败,会执行这里的代码
    
    //  通过可空链式调用设置属性值
    john.residence = Residence()                //  首先实例化属性  john.residence
    john.residence?.address = Address()         //  实例化地址
    //  此时,address 已经存在,才能修改它的属性值
    john.residence?.address?.buildingName = "北京"
    print(john.residence?.address?.buildingIdentifier())     //  输出: Optional("北京")
    print(john.residence!.address!.buildingIdentifier()!)    //  输出:北京
    /*************** 上面的代码必须每一个属性都是强制展开的,任何一个可空时,都会输出  Optional("北京") ***************/
  4. 通过可空链式调用来调用方法

    可以通过可空链式调用来调用方法,并判断是否调用成功。即使这个方法没有返回值。其实没有返回值的方法隐式的返回Void类型。这意味着没有返回值的方法也会返回 () 或者空的元组。

    如果在可空值上通过可空链式调用来调用没有返回值的方法,那么返回的类型将会是可选空类型 Void?,而不是 Void 。因为空过可空链式调用得到的返回值都是可空 nil 的。

    下面几行示例代码结合上面的定义的数据类型,展示出可空返回值可返回空元组的方法的区别:

    //  不在可空链式调用上调用没有返回值的方法,默认是 Void 类型的返回,返回一个空的元组
    let residence = Residence()
    print(residence.printNumberOfRooms())      //  输出:number of rooms : 0     ()
    //  在可空链式调用上调用没有返回值的方法,返回的是一个可选的空类型 Void? ,因为只有可选类型才能为 nil
    let tom = Person()
    print(tom.residence?.printNumberOfRooms())     //  输出: nil

    同样的,可以判断通过可空链式调用来给属性赋值是否成功。首先,需要知道的是无法给一个 nil 的属性赋值:

    let jack = Person()
    let jackResidence = Residence()
    jack.residence? = jackResidence      //  这句话并没有给 jack.residence? 赋值
    //  此时 jack.residence? == nil
    if (jack.residence? = jackResidence) != nil
    print("赋值成功")
     else 
    print("赋值失败")
    
    //  输出: 赋值失败

    作为对比,对比下面这段代码和上面的区别:

    let jack = Person()
    let jackResidence = Residence()
    jack.residence = jackResidence     //  这句话给 jack.residence 了内存地址
    //  此时 jack.residence 已经被实例了
    if (jack.residence? = jackResidence) != nil
    print("赋值成功")
     else 
    print("赋值失败")
    
    //  输出: 赋值成功
  5. 通过可空链式调用来访问下标

    通过可空链式调用,可以用下标来对空值进行读取或写入,并且判断下标是否调用成功。注意:当通过可空链式调用访问可空值得下标的时候,应该将问号放在下标方括号的前面而不是后面。可空链式调用的问号一般直接跟在可控表达式的后面。问号跟在哪个表达式的后面表示哪个表达式是可控的。

    其实访问下标和访问属性、方法并没有什么不同,可用于 if 语句判断,不能给一个值为 nil 的属性赋值:

    //  访问下标
    let rose = Person()
    if let firstRoomName = rose.residence?.rooms[0].name 
    print("因为  rose.residence? 是可空值,并且此时值为 nil, 所以这句话并不会被打印")    //  不执行
     else 
    print("被打印的话")        //  执行
    
    //  给一个值为 nil 的属性赋值
    rose.residence?[0] = Room(roomName: "Living Room")     //  赋值失败,此时 rose.residence?[0] == nil
    rose.residence = Residence()                           //  创建实例
    rose.residence?.rooms.append(Room(roomName: "FirstRoom"))       //  添加值
    //  再次尝试访问下标
    if let firstRoomNameAgain = rose.residence?.rooms[0].name 
    print("因为  rose.residence? 是可空值,并且此时值为 nil, 所以这句话并不会被打印")    //  执行
     else 
    print("被打印的话")        //  不执行
    

    如果下标返回可空类型值,比如 Swift 中 Dictionarykey 下标。可以在下标的闭合括号后面放一个问号来链接下标的可空返回值:

    var testScrore = ["john": [88, 91, 78], "jack": [99, 112, 130]]
    testScrore["john"]?[0] = 140
    testScrore["jack"]?[0] = 33
    testScrore["Hehe"]?[0] = 44      //  这句并不会执行,去掉问号会报错
    print(testScrore)      //  输出: ["jack": [33, 112, 130], "john": [140, 91, 78]]
    
  6. 多层链接

    可以通过多个属性的可空链式调用来向下访问属性,方法以及下标。但是多层可空链式调用不会添加返回值的可空性,也就是说:

    • 如果访问的值不是可空的,通过可空链式调用将会返回可空值。
    • 如果访问值得值是已经可空的,通过可空链式调用并不会变得更空。

    因此:

    • 通过可空链式调用访问一个 Int 值,将会返回 Int? ,不论进行了多少次可空链式调用。
    • 类似的,通过可空链式调用访问 Int? 值,并不会变得更空。
  7. 对返回可空的函数进行链接

    上面的例子说明了如何通过可空链式调用来获取可控的属性值。其实还可以通过可空链式调用来调用返回值是可空值的方法,并且可以继续对空值进行链接:

    //  对返回值可空的方法可以继续往下链接
    let liLei = Person()
    //  注意,在方法的圆括号后面加问号并不是指方法可空,而是指方法的返回值可空。
    if let beginWithPrefix = liLei.residence?.address?.buildingIdentifier()?.hasPrefix("China")  
     else  
    

结束语

这一小节其实是有点唬人的,它只是把我们平常一直用但是没有太在意的事情说了一遍,所以学起来也是挺轻松的。

我人很懒的,一直学习什么的也是办不到。这两天抽空找了一部动漫看看,绝园的暴风雨,整部动漫围绕莎士比亚的哈姆雷特、暴风雨两个复仇剧展开,显然这么高大上的书我是没读过的,但是动漫本身相当不错,角色都很有特点。喜欢看动漫的可以补一下。

以上是关于Swift-可空链式调用(Optional Chaining)(十五)的主要内容,如果未能解决你的问题,请参考以下文章

学习Swift -- 可选链

Swift 或其他语言中的 Optional 术语

Java 8中的Optional 类型与 Kotlin 中的可空类型

Swift 构造方法

Swift16-可选链式调用

Swift16-可选链式调用