也许大家知道空引用(Null references)这个概念,它表示变量没有值。今天我们将一起学习Swift中解决空引用的方法,即Optional – 可选项。可选项是一种非常重要的语言特性,但它可能会让你觉得非常困惑,如果第一遍没有完全明白,我想我们可能需要重复多学几遍,但请不要觉得沮丧,因为初学者对于这个内容都会如此。
本质上讲,可选项试图回答一个问题:“如果我们的变量没有值怎么办?”Swift希望确保我们所有的程序都尽可能安全,因此它有一些非常具体也非常重要的技术来处理这种情况。而可选项即是其中之一。
今天的内容有五大部份,可能有些多,但请务必仔细跟我一起来!
如何使用可选项处理缺失数据
Swift 喜欢可预测性,这意味着它尽可能鼓励我们编写安全的代码,并按照我们期望的方式运行。我们已经见过了抛掷函数(throwing function),但 Swift 还有另一种确保可预测性的重要方法,叫做可选项(optionals)——这个词的意思是 “这个东西可能有值,也可能没有”。
要了解可选项的作用,请看下面的代码:
let opposites = [
"Mario": "Wario",
"Luigi": "Waluigi"
]
let peachOpposite = opposites["Peach"]
在这里,我们创建了一个有两个键的 [String: String] 字典: 马里奥和路易吉。然后,我们尝试读取连接到键 “Peach ”的值,但这个键并不存在,我们也没有提供一个默认值来发送回缺失的数据。
代码运行后,peachOpposite 会是什么呢?这是一个 [String: String] 字典,这意味着键是字符串,值也是字符串,但我们刚刚试图读取一个不存在的键——如果不存在一个值,那么返回一个字符串是没有意义的。
Swift 的解决方案称为可选项(optionals),即可能存在也可能不存在的数据。它们主要通过在数据类型后添加问号来表示,因此在本例中,peachOpposite 将是一个String?,而不是一个String。
可选项就像一个盒子,里面可能有东西,也可能没有。因此,String? 表示里面可能有一个字符串在等着我们,也可能什么都没有——一个叫做 nil 的特殊值,意思是 “无值”。任何类型的数据都可以是可选的,包括 Int、Double 和 Bool,以及枚举、结构和类的实例。
你可能会想 “那么……这里到底发生了什么变化?以前我们有一个String,现在我们有一个String?,但这对我们编写的代码有什么实际改变呢?
这就是关键所在: Swift 希望我们的代码是可预测的,这意味着它不会让我们使用可能不存在的数据。对于可选项,这意味着我们需要解开可选项才能使用它——我们需要查看框内是否有值,如果有,就取出并使用它。
Swift 提供了两种主要的可选项解包方法,但你最常见的是下面这种:
if let marioOpposite = opposites["Mario"] {
print("Mario's opposite is \(marioOpposite)")
}
if let 语法在 Swift 中非常常见,它将创建条件 (if) 和创建常量 (let) 结合在一起。结合起来,它可以做三件事:
- 从字典中读取可选值。
- 如果可选值中包含字符串,它就会被解包——这意味着里面的字符串会被放入 marioOpposite 常量中。
- 条件成功执行——我们成功解包了可选值——因此条件的正文将被运行。
只有在可选项中包含值时,才会运行条件的主体。当然,如果你想添加一个 else 代码块,也是可以的,因为这只是一个普通的条件,所以这样的代码也是可以的:
var username: String? = nil
if let unwrappedName = username {
print("We got a user: \(unwrappedName)")
} else {
print("The optional was empty.")
}
把可选项想象成薛定谔的数据类型:框内可能有一个值,也可能没有,但唯一的办法就是检查。
到目前为止,这可能看起来比较学术化,但可选项确实是帮助我们开发出更好软件的关键。你看,可选项意味着数据可能存在也可能不存在,同样,非可选项——常规字符串、整数等意味着数据必须可用。
想想看:如果我们有一个非可选项 Int,这就意味着里面肯定有一个数字,永远都有。它可能是 100 万或 0,但它仍然是一个数字,而且保证存在。相比之下,可选 Int 设置为 nil 则没有任何值——它不是 0 或任何其他数字,而是什么都没有。
同样,如果我们有一个非可选字符串,这意味着其中肯定有一个字符串:它可能是 “Hello ”或空字符串,但这两种字符串都不同于设置为 nil 的可选字符串。
如果需要,每种数据类型都可以是可选的,包括数组和字典等集合。同样,一个整数数组可能包含一个或多个数字,也可能根本不包含数字,但这两种情况都不同于设置为 nil 的可选数组。
需要明确的是,设置为 nil 的可选整数与持有 0 的非可选整数不同,设置为 nil 的可选字符串与当前设置为空字符串的非可选字符串不同,而设置为 nil 的可选数组与当前没有任何项的非可选数组不同——我们谈论的是没有任何数据,不管是空的还是其他的。
如果你尝试将一个可选整数传递到一个需要非可选整数的函数中,你就可以看到这一点,就像下面这样:
func square(number: Int) -> Int {
number * number
}
var number: Int? = nil
print(square(number: number))
Swift 会拒绝编译该代码,因为可选整数需要解包——我们不能在需要非可选值的地方使用可选值,因为如果里面没有值,我们就会遇到问题。
因此,要使用可选值,我们必须先像这样对其进行解包:
if let unwrappedNumber = number {
print(square(number: unwrappedNumber))
}
在结束之前,我还想提最后一件事:在解包可选项时,将其解包为同名常量是很常见的做法。这在 Swift 中是完全允许的,这意味着我们不必一直将常量命名为 unwrappedNumber 或类似名称。
使用这种方法,我们可以将之前的代码重写成这样:
if let number = number {
print(square(number: number))
}
刚读到这种风格时,你会感到有些困惑,因为现在感觉就像量子物理学——数字真的可以同时可选和不可选吗?当然不是。
这里发生的事情是,我们临时创建了第二个同名常量,它只能在条件的主体中使用。这就是所谓的阴影(shadow),主要用于可选的解包,如上图所示。
因此,在条件的主体中,我们可以使用一个未封装的值——一个真正的 Int 而不是一个可选的 Int?- 但在外部,我们仍然可以使用可选值。
为什么Swift要有可选项?
Swift 的可选项是其最强大的功能之一,同时也是最令人困惑的功能之一。可选项的核心功能很简单:它们允许我们表示某些数据的缺失——字符串不仅是空的,而且是不存在的。
在 Swift 中,任何数据类型都可以是可选的:
- 一个整数可能是 0、-1、500 或任何其他范围的数字。
- 可选整数可能是所有常规整数值,但也可能是 nil —— 它可能不存在。
- 字符串可能是 “Hello”,可能是莎士比亚全集,也可能是“”——空字符串。
- 可选字符串可以是任何常规字符串值,也可以是 nil。
- 自定义 User 结构可以包含描述用户的各种属性。
- 可选的 User 结构可以包含所有这些相同的属性,也可以根本不存在。
区分 “它可能是该类型的任何可能值”和 “它可能是 nil ”是理解可选项的关键,但有时这并不容易。
例如,想想布尔值:布尔值可以是 true 或 false。这意味着一个可选的布尔值可以是 true、false 或两者都不是——它可能什么都不是。这有点难以理解,因为在任何时候,某些东西肯定总是真或假的。
那么,请回答我:我喜欢巧克力吗?除非你是我的朋友,或者在微博上非常关注我,否则你无法确定——你不能肯定地说 “真(我喜欢巧克力)”或 “假(我不喜欢巧克力)”,因为你根本不知道。当然,你可以问我并找出答案,但在问我之前,唯一安全的答案就是 “我不知道”。
如果你想到空字符串“”,这也会让人有点困惑。该字符串不包含任何内容,但这与 nil 并不相同——空字符串仍然是一个字符串。
在学习过程中,可选项可能会让人感觉非常痛苦——你可能会认为 Swift 不需要它们,你可能会认为它们只会碍事,你可能会在每次不得不使用它们的时候磨牙。但请相信我:几个月后,它们就会变得非常有意义,你会怀疑没有它们自己是怎么活下来的!
为什么Swift会让我们解包可选项?
Swift 的可选项可以存储一个值,如 5 或 “Hello”,也可以什么都不存储。正如你可能想象的那样,只有当数字确实存在时,才有可能尝试将两个数字相加,这就是为什么 Swift 不允许我们尝试使用可选项的值,除非我们将它们解包——除非我们查看可选项内部,检查其中是否确实有一个值,然后将值取出来。
在 Swift 中,有几种方法可以做到这一点,但最常见的一种是 if let,就像下面这样:
func getUsername() -> String? {
"Taylor"
}
if let username = getUsername() {
print("Username is \(username)")
} else {
print("No username")
}
getUsername() 函数返回一个可选字符串,这意味着它可能是一个字符串,也可能是 nil。为了便于理解,我让它始终返回一个值,但这并不改变 Swift 的想法——它仍然是一个可选字符串。
if let 这一行结合了很多功能:
- 它调用了 getUsername() 函数。
- 从那里接收可选字符串。
- 它会查看可选字符串内部是否有值。
- 如果确实有一个值(是 “Taylor”),该值将从可选字符串中取出并放入一个新的用户名常量中。
- 然后,条件被视为 true,并打印出 “用户名是 Taylor”。
因此,if let 是一种非常简洁的处理可选项的方法,可以一次性检查并提取所有值。
事实上,它是如此简洁,甚至为我们提供了一个小捷径。与其这样写:
if let number = number {
print(square(number: number))
}
实际上我们可以这样写:
if let number {
print(square(number: number))
}
它的作用完全相同——创建一个数字的阴影副本,只在该条件的正文中进行解包,只是减少了一些重复。
可选项最重要的一个特性是,如果不先解包,Swift 不会让我们使用它们。这为我们所有的应用程序提供了巨大的保护,因为它杜绝了不确定性:当你递给一个字符串时,你知道它是一个有效的字符串;当你调用一个返回整数的函数时,你知道它可以立即安全使用。当你在代码中使用可选项时,Swift 会确保你正确处理它们——检查并解包它们,而不是将不安全的值与已知的安全数据混合在一起。
如何使用guard解包可选项
我们已经见识过 Swift 如何使用 if let 来解包可选项,这也是使用可选项最常见的方法。但还有第二种方法可以做同样的事情,而且几乎同样常见:guard let。
它看起来像这样:
func printSquare(of number: Int?) {
guard let number = number else {
print("Missing input")
return
}
print("\(number) x \(number) is \(number * number)")
}
与 if let 类似,guard let 也会检查可选项中是否有值,如果有,它就会获取该值并将其放入我们选择的常量中。
不过,它这样做的方式会把事情弄反:
var myVar: Int? = 3
if let unwrapped = myVar {
print("Run if myVar has a value inside")
}
guard let unwrapped = myVar else {
print("Run if myVar doesn't have a value inside")
}
因此,如果可选项有值,if let 运行括号内的代码;如果可选项没有值,guard let 运行括号内的代码。这就解释了代码中 else 的用法: “检查我们是否能解开可选项,如果不能,那么……”
我知道这听起来差别不大,但却有着重要的影响。要知道,guard 提供的功能是检查我们的程序状态是否符合我们的预期,如果不符合,就跳出——例如,退出当前函数。
这有时被称为提前返回:我们在函数启动时检查函数的所有输入是否有效,如果有任何输入无效,我们就运行一些代码,然后直接退出。如果所有检查都通过了,我们的函数就能按预期运行。
guard 正是为这种编程风格而设计的,事实上它有两个帮助:
- 如果使用 guard 来检查函数的输入是否有效,如果检查失败,Swift 总是会要求你使用 return。
- 如果检查通过,并且正在解包的可选项内部有一个值,则可以在 guard 代码结束后使用该值。
如果你看看前面的 printSquare() 函数,就会明白这两点的作用:
func printSquare(of number: Int?) {
guard let number = number else {
print("Missing input")
// 1: We *must* exit the function here
return
}
// 2: `number` is still available outside of `guard`
print("\(number) x \(number) is \(number * number)")
}
因此:使用 if let 来解包可选项,以便以某种方式对其进行处理;使用 guard let 来确保可选项中包含某些内容,否则就退出。
提示:你可以在任何条件下使用 guard,包括不解包可选项的条件。例如,你可以使用 guard someArray.isEmpty else { return }。
何时使用 guard let 而不是 if let
Swift 为我们提供了一种 if let 的替代方法,叫做 guard let,它也会在可选项包含值的情况下对其进行解包,但工作原理略有不同:guard let 的设计目的是在检查失败时退出当前函数、循环或条件,因此使用它解包的任何值都会在检查后继续存在。
为了说明两者的区别,下面是一个以可选整数形式返回生命意义的函数:
func getMeaningOfLife() -> Int? {
42
}
下面是该函数在另一个名为 printMeaningOfLife() 的函数中的应用:
func printMeaningOfLife() {
if let name = getMeaningOfLife() {
print(name)
}
}
如果使用 if let,getMeaningOfLife() 的结果只有在返回整数而不是 nil 时才会被打印出来。
如果我们使用 guard let 来编写,结果会是这样的:
func printMeaningOfLife() {
guard let name = getMeaningOfLife() else {
return
}
print(name)
}
是的,有点长,但有两件重要的事情发生了变化:
- 它让我们专注于 “幸福之路”——当一切都按计划进行时,函数的行为,也就是打印生命的意义。
- guard 要求我们在使用函数时退出当前作用域,这意味着如果函数失败,我们必须从函数中返回。这不是可选的: 如果不返回,Swift 不会编译我们的代码。
在方法的开头使用一个或多个 guard 是很常见的,因为它用于验证某些条件是否正确。这使得我们的代码更容易阅读,而不是先检查一个条件,然后运行一些代码,再检查另一个条件,然后运行一些不同的代码。
因此,如果只是想解开一些可选项,可以使用 if let,但如果是在继续之前专门检查条件是否正确,则更倾向于使用 guard let。
如何使用 nil聚合(nil coalescing)解包可选项
等等……Swift 还有第三种解包可选项的方法?没错!它被称为nil聚合操作符,可以让我们解除对可选项的包裹,并在可选项为空的情况下提供一个默认值。
让我们倒退一下:
let captains = [
"Enterprise": "Picard",
"Voyager": "Janeway",
"Defiant": "Sisko"
]
let new = captains["Serenity"]
这将在我们的captains字典中读取一个不存在的键,这意味着 new 将是一个可选字符串,并将其设置为 nil。
通过 nil 聚合操作符(写成 ??),我们可以为任何可选项提供一个默认值,就像这样:
let new = captains["Serenity"] ?? "N/A"
这将从captains字典中读取值,并尝试将其解包。如果可选项中有一个值,它就会被送回并存储在 new 中,但如果没有,就会使用 “N/A ”来代替。
这就意味着,无论可选项包含什么(值或 nil),new 的最终结果都将是一个真正的字符串,而不是一个可选项。这可能是 captains 值中的字符串,也可能是 “N/A”。
我知道你在想什么:难道我们不能在从字典读取数据时指定一个默认值吗?如果你这么想,那就完全正确了:
let new = captains["Serenity", default: "N/A"]
结果完全相同,这可能会让人觉得 nil 聚合操作符毫无意义。然而,nil 聚合操作符不仅适用于字典,还适用于任何可选项。
例如,数组上的 randomElement() 方法会从数组中随机返回一个项目,但它返回的是一个可选项,因为你可能会在一个空数组上调用它。因此,我们可以使用 nil coalescing 来提供默认值:
let tvShows = ["Archer", "Babylon 5", "Ted Lasso"]
let favorite = tvShows.randomElement() ?? "None"
或者,你有一个带有可选属性的结构,希望在缺少该属性时提供一个合理的默认值:
struct Book {
let title: String
let author: String?
}
let book = Book(title: "Beowulf", author: nil)
let author = book.author ?? "Anonymous"
print(author)
甚至在从字符串创建整数时也很有用,因为转换可能失败——你可能提供了一个无效的整数,如 “Hello”。在这里,我们可以使用 nil coalescing 来提供一个默认值,就像这样:
let input = ""
let number = Int(input) ?? 0
print(number)
正如你所看到的,nil 聚合操作符在任何情况下都很有用,只要你有一个可选项,并想使用其中的值,或者在缺少该值时提供一个默认值。
什么时候应该在 Swift 中使用 nil 聚合?
Nil coalescing 可以让我们尝试解开可选项,但如果可选项包含 nil,则会提供一个默认值。这在 Swift 中非常有用,因为虽然可选项是一个很棒的功能,但通常最好是非可选项,即一个真正的字符串,而不是一个 “可能是字符串,可能是 nil ”的字符串。
例如,如果你正在开发一个聊天应用程序,并希望加载用户保存的任何信息草稿,你可能会写这样的代码:
let savedData = loadSavedMessage() ?? ""
因此,如果 loadSavedMessage() 返回一个内部包含字符串的可选项,它就会被解包并放入 savedData。但如果可选项为 nil,那么 Swift 会将 savedData 设置为空字符串。无论如何,savedData 最终都将是String,而不是String?。
如果你愿意,可以使用链式 nil coalescing,不过我认为这种方法并不常见。因此,如果你愿意,这种代码是有效的:
let savedData = first() ?? second() ?? ""
这将尝试运行 first(),如果返回 nil,则尝试运行 second(),如果返回 nil,则使用空字符串。
请记住,读取字典键值总是会返回一个可选项,因此你可能需要在此处使用 nil coalescing 来确保返回一个非可选项:
let scores = ["Picard": 800, "Data": 7000, "Troi": 900]
let crusherScore = scores["Crusher"] ?? 0
这绝对是一个品味问题,但字典提供了一种稍有不同的方法,让我们可以指定键未找到时的默认值:
let crusherScore = scores["Crusher", default: 0]
你可以选择自己喜欢的方式——在读取字典值时,两者并无本质区别。
如何使用可选项链处理多个可选项
可选链是一种简化的语法,用于读取可选项内部的可选项。这听起来可能是你很少会用到的东西,但如果我给你举个例子,你就会明白为什么它很有用了。
请看这段代码:
let names = ["Arya", "Bran", "Robb", "Sansa"]
let chosen = names.randomElement()?.uppercased() ?? "No one"
print("Next in line: \(chosen)")
这同时使用了两个可选项:你已经看到了 nil 聚合运算符是如何在可选项为空时帮助提供默认值的,但在此之前,你看到了可选链,在这里我们有一个问号,后面跟着更多的代码。
通过可选链,我们可以说 “如果可选项内部有一个值,那么就把它拆开……”,然后我们可以添加更多代码。在我们的例子中,我们说的是 “如果我们设法从数组中获取了一个随机元素,那么就把它大写”。请记住,randomElement() 返回的是一个可选项,因为数组可能是空的。
可选项链的神奇之处在于,如果可选项为空,它不会做任何事情——它只会返回之前的可选项,仍然是空的。这意味着可选链的返回值始终是一个可选项,这就是为什么我们仍然需要 nil coalescing 来提供一个默认值。
可选链的长度可以随心所欲,只要有任何部分返回 nil,代码的其余部分就会被忽略并返回 nil。
举个例子来说明可选链的作用:我们想根据作者姓名按字母顺序排列书籍。如果我们将其分解,那么:
- 我们有一个图书结构体的可选实例——我们可能有一本书要排序,也可能没有。
- 这本书可能有作者,也可能是匿名的。
- 如果有作者字符串,它可能是空字符串,也可能是文本,因此我们不能总是依赖于第一个字母。
- 如果第一个字母存在,请确保它是大写字母,这样,名字小写的作者(如 bell hooks)就能正确排序。
如下所示:
struct Book {
let title: String
let author: String?
}
var book: Book? = nil
let author = book?.author?.first?.uppercased() ?? "A"
print(author)
因此,它的意思是 “如果我们有一本书,这本书有一个作者,而作者有一个首字母,那么就把它大写并送回,否则就送回 A”。
诚然,你不可能在可选项上钻研那么深,但我希望你能看到语法的简短是多么令人赏心悦目!
为什么可选项链如此重要?
Swift 的可选项链让我们可以在一行代码中挖掘多层可选项,如果其中任何一层为空,那么整行代码都会变为空。
举个简单的例子,我们可能有一个姓名列表,并希望根据姓氏的第一个字母找到他们的位置:
let names = ["Vincent": "van Gogh", "Pablo": "Picasso", "Claude": "Monet"]
let surnameLetter = names["Vincent"]?.first?.uppercased()
在这里,由于 names[“Vincent”] 可能不存在,我们对字典值使用了可选项链,而在读取姓氏的第一个字符时,由于字符串可能是空的,我们又使用了可选链。
可选链是 nil coalescing 的好帮手,因为它允许你挖掘层层可选项,同时还能在任何一个可选项为 nil 时提供合理的退路。
回到姓氏的例子,如果我们无法读取某人姓氏的第一个字母,我们可以自动返回“?”:
let surnameLetter = names["Vincent"]?.first?.uppercased() ?? "?"
有关可选链的更多信息,我向你推荐 Andy Bargh 的这篇文章:https://andybargh.com/optional-chaining/。
如何使用可选项处理函数失效
当我们调用一个可能会出错的函数时,要么使用 try 调用它并适当处理错误,要么如果我们确定函数不会失败,就使用 try! (剧透:你应该很少使用 try!)。
不过,还有另一种方法:如果我们只关心函数是成功还是失败,我们可以使用可选的 try,让函数返回一个可选值。如果函数运行时没有抛出任何错误,那么可选值将包含返回值,但如果抛出了任何错误,函数将返回 nil。这意味着我们无法确切知道抛出了什么错误,但通常这并无大碍——我们可能只关心函数是否正常运行。
如下所示:
enum UserError: Error {
case badID, networkFailed
}
func getUser(id: Int) throws -> String {
throw UserError.networkFailed
}
if let user = try? getUser(id: 23) {
print("User: \(user)")
}
getUser() 函数总是会抛出 networkFailed 错误,这对我们的测试目的来说没有问题,但我们实际上并不关心抛出的是什么错误,我们关心的只是调用是否发回了用户。
这就是 try? 的作用所在:它使 getUser() 返回一个可选字符串,如果有任何错误抛出,该字符串将为零。如果你想知道到底发生了什么错误,那么这种方法就没用了,但很多时候我们并不关心。
如果你愿意,可以将 try? 与 nil coalescing 结合起来,这意味着 “尝试从该函数获取返回值,但如果失败,则使用该默认值”。
不过请注意:你需要在 nil coalescing 之前添加一些括号,以便 Swift 能准确理解您的意思。例如,你可以这样写:
let user = (try? getUser(id: 23)) ?? "Anonymous"
print(user)
你会发现 try? 主要用于三个地方:
- 与 guard let 结合使用,可在 try? 调用返回 nil 时退出当前函数。
- 与 nil coalescing 结合使用,以尝试某些操作或在失败时提供一个默认值。
- 在调用任何没有返回值的抛出函数时,当你真的不在乎它是否成功时——例如,你可能正在向日志文件写入内容或向服务器发送分析结果。
什么时候应该使用可选项try?
我们可以在 Swift 中使用 do、try 和 catch 运行抛出函数,但另一种方法是使用 try? 将抛出函数调用转换为可选项。如果函数成功执行,其返回值将是一个可选项,其中包含你通常会收到的返回值;如果函数失败,其返回值将是一个设置为 nil 的可选项。
使用可选 try 有好处也有坏处,但主要是看错误对你来说有多重要。如果你想运行一个函数,并且只关心它成功或失败——你不需要区分它可能失败的各种原因——那么使用可选 try 就非常合适,因为你可以把整件事归结为 “它成功了吗?”
因此,与其这样写:
do {
let result = try runRiskyFunction()
print(result)
} catch {
// it failed!
}
你可以这样写:
if let result = try? runRiskyFunction() {
print(result)
}
如果这是你想要做的,那么你可以让 runRiskyFunction() 返回一个可选项,而不是抛出错误,但抛出错误和使用可选 try 确实给了我们灵活性,让我们可以在以后改变主意。也就是说,如果我们编写了一个会抛出错误的函数,那么在调用现场,我们可以根据当时的需要使用 try/catch 或使用可选 try。
无论如何,可选 try能让你更专注于手头的问题。
总结
在这几章中,我们学习了 Swift 最重要的功能之一,虽然大多数人一开始都觉得可选项难以理解,但几乎每个人都认为它们在实践中非常有用。
让我们回顾一下所学内容:
- 可选项让我们可以表示没有数据,这意味着我们可以说 “这个整数没有值”——这与 0 这样的固定数字不同。
- 因此,所有不是可选项的东西内部肯定都有一个值,即使那只是一个空字符串。
- 拆开可选项的过程就像在一个盒子里查看里面有什么:如果里面有一个值,它就会被送回去使用,否则里面就是 nil。
- 如果可选项有值,我们可以使用 if let 来运行代码;如果可选项没有值,我们可以使用 guard let 来运行代码,但使用 guard 时,我们必须在之后退出函数。
- nil 聚合操作符??可以解包并返回可选项的值,或使用默认值。
- 通过可选项链,我们可以用方便的语法读取另一个可选项内部的可选项。
- 如果一个函数可能会出错,可以使用 try? ——返回函数的返回值,或者在抛出错误时返回 nil。
说到语言特性,可选项是仅次于闭包的让人难以掌握的特性,但我保证,几个月后你就会发现,没有可选项你将不知如何编写程序!