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. RNN推导

    http://www.cnblogs.com/YiXiaoZhou/p/6058890.html RNN求解过程推导与实现 RNN LSTM BPTT matlab code opencv code ...

  2. 【转载】GitHub中国区前100名到底是什么样的人

    转载了这篇文章: http://www.jianshu.com/p/d29cba7934c9 这篇文章真是太牛了!转载过来涨涨见识,同时好好励志一把.还有,ruanyifeng怎么长那样... 哈 另 ...

  3. SocketIO总结

    我在马克飞象上写的一样的内容,感觉那个样式好看的:WorkerMan的部分总结 workerman中部分函数总结 以下是把我搜集到的资料进行了一个整合.详细怎么使用.慢慢摸索吧. Worker类 中文 ...

  4. stateMachine 相关知识

    一个state的基本构造,processMessage 以及可选的enter exit 和getName. processMessager是用于处理数据. enter 和exit 则是类似于 面向编程 ...

  5. C#如何生成release版本的程序,生成debug版本的程序

    除了右击项目在生成中配置改成Release还要在顶部切换成Release                                  

  6. Laravel 5.4建站06--API 认证系统 Passport

    介绍 在 Laravel 中,实现基于传统表单的登陆和授权已经非常简单,但是如何满足 API 场景下的授权需求呢?在 API 场景里通常通过令牌来实现用户授权,而非维护请求之间的 Session 状态 ...

  7. mac osx 下编译 OpenWrt

    默认的文件系统hfs大小写不敏感.新建一个磁盘镜像文件并合式化为hfs+, 然后挂载到系统中. hdiutil create -size 20g -fs "Case-sensitive HF ...

  8. 查看客户端java日志

    通过 Java 控制面板启用 Java 控制台 Windows 8 使用搜索来查找控制面板 按 Windows 徽标键 + W 以打开搜索框来搜索设置,或者将鼠标指针拖动到屏幕的右下角,然后单击搜索图 ...

  9. vs2015编译EasyDarwin开源流媒体服务器Linux版本调研

    本文转自EasyDarwin团队成员Alex的博客:http://blog.csdn.net/cai6811376/article/details/51843196 之前InfoQ的一篇文章提到用vs ...

  10. Part of defining a topology is specifying for each bolt which streams it should receive as input

    http://storm.apache.org/ [doing for realtime processing what Hadoop did for batch processing ] Apach ...