前言
其实可空链式调用并没有它的名字那么陌生,简而言之就是对于可选类型Optional
(使用问号 ? 后缀表示)和强制展开类型(使用感叹号 ! 后缀表示)的使用方法。在平常写代码的时候只是大概的清楚哪些值是可空的,哪些值是肯定存在的,但是并没有深究可空的调用有何优点,有何使用时需要注意的事项。至少前面写不少示例代码的时候,我也是大都按照自己的想法去定义的。这一小节就是对可空调用的详细描述,至于链式,就是多层调用,大概就是school.classroom.student.name
这样的调用方法。
小节知识点,看着挺多挺长,但是如果把可空链式调用
这几个忽略掉,就会发现都是我们已经非常熟悉的基本用法:
- 使用可空链式调用来强制展开
- 使用可空链式调用定义模型类
- 通过可空链式调用访问属性
- 通过可空链式调用来调用方法
- 通过可控调用方法来访问下标
- 多层链接
- 对返回可空值得函数进行链接
可空链式调用是一种可以请求和调用属性、方法以及下标的过程,它的可空性体现在请求和调用的目前当前可能为空nil
。如果可空的目标有值,那么就会调用成功;如果选择的目标为空,那么这种调用将会返回空nil
。多个连续的调用可以被链接到一起形成一个调用链,如果其中任何一个节点为空nil
,将会导致整个调用失败。
Swift的可空链式调用和Objective-C中的消息为空有点像,但是Swift可以使用在任何类型中,并且能够检查调用是否成功。一个可空调用相当于Objective-C中if (message == nil) { } else { }
,只是Objective-C中只能用在对象或者变量中,远没有Swift中功能强大。
分条详述
- 使用可空链式调用来强制展开
下面这几句话读起来有点拗口,多理解就好:通过在想调用非空的属性、方法、或下标的可空值
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 }
- 通过可空链式调用定义模型类
通过使用可空链式调用可以调用多层属性、方法和下标。这样可以通过各种模型向下访问各种子属性。并且可以根据是否为空判断是否可以访问子属性的属性、方法或下标。
下面示例代码定义了四个模型类,这些例子包括多层可空链式调用。同时,下面几个知识点也会以这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 } } }
- 通过可空链式调用访问属性
就是我们平常调用属性、方法的那一套,先创建一个实例,然后去调用。其实,很多时候实例、属性等是可空的还是强制展开的,系统都会帮我们去辨别的,我们需要去理解为什么会出现补全的情况,有时候需要我们手动去决定可选还是强制展开。
下面是代码示例,主要注意点都写在注释里了:
// 创建一个户主实例 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("北京") ***************/
- 通过可空链式调用来调用方法
可以通过可空链式调用来调用方法,并判断是否调用成功。即使这个方法没有返回值。其实没有返回值的方法隐式的返回
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("赋值失败") } // 输出: 赋值成功
- 通过可空链式调用来访问下标
通过可空链式调用,可以用下标来对空值进行读取或写入,并且判断下标是否调用成功。注意:当通过可空链式调用访问可空值得下标的时候,应该将问号放在下标方括号的前面而不是后面。可空链式调用的问号一般直接跟在可控表达式的后面。问号跟在哪个表达式的后面表示哪个表达式是可控的。
其实访问下标和访问属性、方法并没有什么不同,可用于
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 中
Dictionary
的key
下标。可以在下标的闭合括号后面放一个问号来链接下标的可空返回值: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]]
- 多层链接
可以通过多个属性的可空链式调用来向下访问属性,方法以及下标。但是多层可空链式调用不会添加返回值的可空性,也就是说:
- 如果访问的值不是可空的,通过可空链式调用将会返回可空值。
- 如果访问值得值是已经可空的,通过可空链式调用并不会变得更空。
因此:
- 通过可空链式调用访问一个
Int
值,将会返回Int?
,不论进行了多少次可空链式调用。 - 类似的,通过可空链式调用访问
Int?
值,并不会变得更空。
- 对返回可空的函数进行链接
上面的例子说明了如何通过可空链式调用来获取可控的属性值。其实还可以通过可空链式调用来调用返回值是可空值的方法,并且可以继续对空值进行链接:
// 对返回值可空的方法可以继续往下链接 let liLei = Person() // 注意,在方法的圆括号后面加问号并不是指方法可空,而是指方法的返回值可空。 if let beginWithPrefix = liLei.residence?.address?.buildingIdentifier()?.hasPrefix("China") { } else { }
结束语
这一小节其实是有点唬人的,它只是把我们平常一直用但是没有太在意的事情说了一遍,所以学起来也是挺轻松的。
我人很懒的,一直学习什么的也是办不到。这两天抽空找了一部动漫看看,绝园的暴风雨,整部动漫围绕莎士比亚的哈姆雷特、暴风雨两个复仇剧展开,显然这么高大上的书我是没读过的,但是动漫本身相当不错,角色都很有特点。喜欢看动漫的可以补一下。