Optional Chaining介绍

关于「optional chaining」,《The Swift Programming Language》是这么描述的:

Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil. If the optional contains a value, the property, method, or subscript call succeeds; if the optional is nil, the property, method, or subscript call returns nil. Multiple queries can be chained together, and the entire chain fails gracefully if any link in the chain is nil.

P.S:「Optional Chaining」有各种各样的翻译,譬如「自判断链接」「可选链表」等,个人觉得「可选链表」还凑合,本文尽量使用英文表述。

P.S:Swift中的「optional chaining」有些类似于OC中「向nil发送消息」,简而言之,在OC中,向一个对象(指针)发送消息时,若这个指针为nil,则啥都不干;Swift的「optional chaining」有类似的效果;只不过OC这种处理只适用于类对象,Swift的「optional chaining」适用于任意类型。此外,在OC中,我们想一个可能为nil的对象发送一个消息时,通常我们不知道这个消息是否被执行了(尤其是该消息么有返回值时),但在Swift中,可以通过检查返回值来判断实例的方法是否被调用,后文会详细阐述。

可选链表 v.s 强制解包

在Swift中,访问某些属性/方法/下标得到的经果常常是一个optional,在这些返回值为optional的属性/方法/下标后放上一个问号?就构成了所谓的「optional chaining」。
这有一些类似于强制解包(在可选类型后面放上一个感叹号!强制解包)。他们的在于当optional为nil时「optional chaining」即刻失败,然而一般的强制解包操作会引发runtime错误。

下面这段话非常重要。
因为「optional chaining」是随时否可以提前返回nil的,所以使用optional chaining所得到的东西都是optional,even if the property, method, or subscript you are querying returns a non-optional value. 下面以代码来演示:

class Toy {
let name: String
init(name: String) {
self.name = name
}
} class Pet {
var toy: Toy?
} class Child {
var pet: Pet?
}

在实际使用过程中,我们想要知道小明(名为xiaoming的Child对象)的宠物的玩具的名字的时候,可以通过下面的「optional chaining」查询:

let toyName = xiaoming.pet?.toy?.name

注意,我们最后访问的是name,并且在Toy的定义中name是被定义为一个确定的String而非String?的,但是我们拿到的toyName起始还是一个String?的类型。这是由于在「optional chaining」中我们在任意一个?.的时候都可能遇到nil而提前返回,这个时候当然只能获取到nil了。

所以,在实际使用中,我们大多数情况下可能更希望使用「Optional Binding」来直接取值,如下:

if let toyName = xiaoming.pet?.toy?.name {
// 巴拉巴拉
}

总之,使用「optional chaining」的返回结果一定是一个optional。

OK,现在以几段代码来解释「可选链表」(即?.)和「强制解包」(即!.)的不同。

首先定义两个类Person和Residence,如下:

class Person {
var residence: Residence?
} class Residence {
var numberOfRooms =
}

Residence具有一个Int类型属性numberOfRooms,其值为1。Person具有一个optional属性residence,它的类型是Residence?

如果你创建一个新的Person实例,它的residence属性由于是被定义为自判断型的,此属性将默认初始化为空:

let john = Person()

如果你想使用声明符!强制解包获得这个人residence属性的numberOfRooms属性值,将会引发运行时错误,因为这时没有可以供拆包的residence值,如下:

let roomCount = john.residence!.numberOfRooms
// 将导致运行时错误

当john.residence不是nil时,会正常运行,且会将roomCount设置为一个Int类型的合理值。然而,如上所述,当residence为空时,这个代码将会导致运行时错误。

自判断链接提供了一种另一种获得numberOfRooms的方法。利用自判断链接,使用问号来代替原来!的位置:

if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
} /* 输出:
Unable to retrieve the number of rooms.
*/

通过Optional Chaining访问属性

正如上文可选链表 v.s 强制解包所述,你可以利用「optional chaining」获取属性,并且检查属性是否成功。

使用上述定义的类来创建一个人实例,并再次尝试后去它的numberOfRooms属性:

let john = Person()
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
/*输出:
Unable to retrieve the number of rooms.
*/

由于john.residence是空,所以这个自判断链接和之前一样失败了,但是没有运行时错误。

你还可以使用「optional chaining」来设置属性值,如下:

john.residence?.numberOfRooms = 

通过Optional Chaining调用方法

你可以使用「optional chaining」的调用某个optional的方法,并可以检查方法调用是否成功,哪怕这个方法没有返回值。

在上文的Residence中添加一个实例方法printNumberOfRooms,该方法会打印numberOfRooms的当前值。方法如下:

func printNumberOfRooms(){
println("The number of rooms is \(numberOfRooms)")
}

这个方法没有返回值。但是,在Swift中,若函数和方法没有显式提供返回值,则Swift会为它们提供一个隐式的返回值类型Void。如果你利用自判断链接调用此方法,这个方法的返回值类型将是Void?,而不是Void,因为当通过「optional chaining」调用方法时返回值总是optional,即使是这个方法本是没有定义返回值,你也可以使用if语句来检查是否能成功调用printNumberOfRooms方法:如果方法通过自判断链接调用成功,printNumberOfRooms的隐式返回值将会是Void,如果没有成功,将返回nil:

if john.residence?.printNumberOfRooms() != nil {
println("It was possible to print the number of rooms.")
} else {
println("It was not possible to print the number of rooms.")
}
/*输出:
It was not possible to print the number of rooms.
*/

通过Optional Chaining访问下标

还可以在「optional chaining」中通过「下标」(subscripts)获取和设置一个optional,同样可以检查是否获取成功;

通过「Optional Chaining」访问下标时,一定要注意:

When you access a subscript on an optional value through optional chaining, you place the question mark (?before the subscript’s braces ([]), not after. The optional chaining question mark always follows immediately after the part of the expression that is optional.

举个栗子,在上文Person和Residence的基础上添加一个新的类Room:

class Room {
let name: String
init(name: String) {
self.name = name
}
}

在Residence类中添加一个subscript和一个数组属性rooms,如下:

class Residence {
...
var rooms = [Room]()
subscript(i: Int) -> Room {
return rooms[i]
}
...
}

通过「Optional Chaining」访问下标如下:

if let firstRoomName = john.residence?[].name {
println("The first room name is \(firstRoomName).")
} else {
println("Unable to retrieve the first room name.")
} /*输出:
Unable to retrieve the first room name.
*/

同样,除了获取,还可以设置:

john.residence?[] = Room(name: "Bashroom")

Swift Optional Chaining的更多相关文章

  1. Swift中可选型的Optional Chaining 和 Nil-Coalesce(Swift2.1)

    /* 下面是介绍Optional Chaining 和 Nil-Coalesce */ // Optional Chaining (可选链) if let errorMessage = errorMe ...

  2. Swift Optional

    拆包和解包的原因: 其实所谓的 nil 就是 Optional.None, 非 nil 就是Optional.Some, 然后会通过Some(T)包装(wrap)原始值,这也是为什么在使用 Optio ...

  3. Welcome-to-Swift-17自判断链接(Optional Chaining)

    自判断链接(Optional Chaining)是一种可以请求和调用属性.方法及子脚本的过程,它的自判断性体现于请求或调用的目标当前可能为空(nil).如果自判断的目标有值,那么调用就会成功:相反,如 ...

  4. Optional Chaining as an Alternative to Forced Unwrapping

    ?与!的区别 You specify optional chaining by placing a question mark (?) after the optional value on whic ...

  5. 精读《Optional chaining》

    1. 引言 备受开发者喜爱的特性 Optional chaining 在 2019.6.5 进入了 stage2,让我们详细读一下草案,了解一下这个特性的用法以及讨论要点. 借着这次精读草案,让我们了 ...

  6. Swift -> Optional嵌套 探讨

    准备运动:Optional 的介绍 王巍的<Swifter>一书中,介绍了一个有用的命令:在 LLDB 中输入 fr v -R foo,可以查看foo 这个变量的内存构成.我们稍后的分析将 ...

  7. [TypeScript] Optional Chaining with TypeScript 3.7

    TypeScript 3.7 adds support for optional chaining. This lesson shows you how to use it in your code ...

  8. js optional chaining operator

    js optional chaining operator js 可选链 可选链操作符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效. ?. 操作符的功能类似于 ...

  9. TypeScript 3.7 RC & Optional Chaining

    TypeScript 3.7 RC & Optional Chaining https://devblogs.microsoft.com/typescript/announcing-types ...

随机推荐

  1. Aws Dynamodb数据导出到S3

    本节将描写叙述怎样从一个或多个DynamoDB的表导出数据到S3的bucket中.在运行导出之前你须要提前创建好S3的bucket. 注意 假设你还没有使用过AWS Data Pipeline,在运行 ...

  2. Toolbar的使用.md

    1.什么是Toolbar Toolbar是在Android5.0时出现的一个新控件,其目的用于取代Actionbar,它与Actionbar最大的差别就是Toolbar使用更加灵活.自由,而且Tool ...

  3. sql 表的部分字段查找 的结果集

    传统sql从多个对象中获得的list<Object> ,可以这样处理(利用Map)  List list = query.getList(sql);  //封装成BB类型  List< ...

  4. Effective C++ 条款一 视C++为一个语音联邦

    1.C语言         区块.语句.预处理器.内置数据类型.数组.指针等内容 2.OC++       类.封装.继承.多态.virtual函数 等 3.Template C++       泛型 ...

  5. python(3)- 循环语句:从最内层跳出多层循环

    跳出多层循环:三层循环,最里层直接跳出3层 方法一: 在Python中,函数运行到return这一句就会停止,因此可以利用这一特性,将功能写成函数,终止多重循环 def work(): #定义函数 f ...

  6. 【每日Scrum】第四天(4.25) TD学生助手Sprint2站立会议

    站立会议 组员 昨天 今天 困难 签到 刘铸辉 (组长) 和小楠重写架构,使代码更加简洁,并增加了几个界面 今天增加了几个页面的子菜单,然后设计了几个要用的界面 遇到的困难是,菜单的事件处理真是神一样 ...

  7. 一起talk GDB吧(第二回:GDB单步调试)

    各位看官们,大家好.我们在上一回中说简单地介绍了GDB.这一回中,我们介绍GDB的调试功能:单步 调试. 闲话休提,言归正转. 让我们一起talk GDB吧! 看官们,我们先说一下什么是单步调试.大家 ...

  8. pppoe server 搭建

    Ubuntu 上搭建 pppoe server sudo apt-get install pppoe $ cat /etc/ppp/pppoe-server-options # PPP options ...

  9. 顺序容器vector,deque,list的选用规则

    前言 常见的顺序容器主要有三种 - vector,deque,list.它们实现的功能相差不大,那么实际开发中该如何进行选择呢?本文将为你解答这个问题. 分析 由于这三种容器实现的数据结构原型不同(v ...

  10. 游戏开发之基础图像---3d图像处理

    http://dev.gameres.com/Program/Visual/3D/3Darit.htm float 类型数据有效数字是小数点后面6位 单精度 doluble 类型数据是至少10位,双精 ...