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. scala工具sbt的安装和使用;idea如何创建scala项目

    scala的sbt类似于java的maven mac:brew install sbt linux:yum Install sbt 或者下载二机制包 使用sbt需要想mvn一样搭建公司私服,不然,下载 ...

  2. Linux包括hash_map和hash_set的not declared问题

    当在Linux下cpp文件包括hash_map或hash_set时.会出现"'hash_map' was not declared in this scope"问题. #inclu ...

  3. redis 在我做的容器中的配置路劲

    配置 /etc/redis/redis.conf 数据库位置 /var/lib/redis/dump.rdb

  4. 实现TextView中link的点击效果

    朋友们,你们在TextView处理link的时候是不是一直被苦逼的android默认的方式困扰?每次点击link的时候,点击效果是整个textview来响应.非常烂吧?原因就不多赘述了. 那么以下这个 ...

  5. 如何打造你的独特观点(一) ——形成“自己的想法”的基础课zz

    信息过载的时代,能在各种KOL的声音中保持独立思考很不容易,能输出独特观点又进一层.不断练习我们独立思考的能力,有助于看清周围复杂的事物,也能让我们在日常生活中给人留下“有趣之人”的印象,提升人际交往 ...

  6. iOS之简单瀑布流的实现

    iOS之简单瀑布流的实现   前言 超简单的瀑布流实现,这里说一下笔者的思路,详细代码在这里. 实现思路 collectionView能实现各中吊炸天的布局,其精髓就在于UICollectionVie ...

  7. mysql 中创建递归函数

    1.在navicat中创建递归函数 BEGIN ) DEFAULT ''; ) default ''; ) default ''; ) default rootId; ; WHILE rootId i ...

  8. leetCode(51):Valid Palindrome

    Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignori ...

  9. 学习某些API的方法

    学习某些 API 的方法 这里的 API 可能是某个系统平台,开发包,开发平台,开发工具等等,因为任何系统和技术方法提供给开发者的打包方式都是一系列 API . 无论你有在哪一层级开发,从硬件驱动到系 ...

  10. 计算机鼻祖-Donald Knuth(高纳德) 的传奇

    李开复说,练内功,不要仅仅花功夫学习各种流行的编程语言和工具,以及一些公司招聘广告上要求的科目.要把数据结构.算法.数据库.操作系统原理.计算机体系结构.计算机网络,离散数学等基础课程学好.最好还是试 ...