可选链 Optional Chaining
本教程共 93 篇 · 第 42 篇 · 更新于 2026-08-08 · 约 8 分钟阅读
本节目标:学会用
?.在可选值上安全地访问属性、调用方法、取下标,明白”任何一环为 nil,整条链返回 nil”的机制。
强制解包 ! 太危险,那有没有一种方式,既能顺着访问可选值内部的成员,又不会因为中间某个环节是 nil 而崩溃?有,这就是可选链(Optional Chaining)。它是日常代码里出现频率极高的语法。
1-1 可选链是什么
可选链是一种”沿着可选值查询并调用其属性、方法、下标”的过程。写法是:在可选值后面加一个问号 ?,再接你要访问的成员。如果可选值有内容,访问就成功;如果它是 nil,整条访问就优雅地返回 nil,不会崩。
对比一下强制解包:
class Room {
let name: String
init(name: String) { self.name = name }
}
class Address {
var buildingName: String?
var street: String?
func buildingIdentifier() -> String? {
if let street { return street }
return buildingName
}
}
class Residence {
var rooms: [Room] = []
var numberOfRooms: Int { rooms.count }
var address: Address?
func printNumberOfRooms() {
print("房间数是 \(numberOfRooms)")
}
subscript(i: Int) -> Room {
get { rooms[i] }
set { rooms[i] = newValue }
}
}
class Person {
var residence: Residence?
}
let john = Person()
let roomCount = john.residence!.numberOfRooms // 崩溃:residence 是 nil
john.residence 默认是 nil,用 ! 强解直接崩。换成可选链:
if let roomCount = john.residence?.numberOfRooms {
print("有 \(roomCount) 个房间")
} else {
print("取不到房间数") // 会走这里
}
?. 表示:如果 residence 有值就继续取 numberOfRooms,没有就整条返回 nil。程序不再崩溃。
1-2 可选链的结果永远是可选类型
这是最容易被忽略的一点:不管你链上访问的成员原本是不是可选,只要经过了可选链,返回结果一定是可选类型。比如 numberOfRooms 本身是普通 Int,但通过 john.residence?.numberOfRooms 取出来的是 Int?。
原因很合理:residence 可能是 nil,那结果自然”可能没有”,所以包成可选。这意味着你拿到结果后,往往还要配合 if let 或 ?? 进一步处理,而不能直接当普通值用。
1-3 通过可选链调用方法和设值
可选链不止能取属性,还能调方法、设属性。调一个没有返回值的普通方法,也可以通过可选链判断”调用成不成功”:
if john.residence?.printNumberOfRooms() != nil {
print("成功调用了方法")
} else {
print("没调用成功") // residence 为 nil 时走这里
}
通过可选链设属性也同理,赋值表达式本身返回 Void?,可以用它判断是否设值成功:
let someAddress = Address()
if (john.residence?.address = someAddress) != nil {
print("设值成功")
}
如果 residence 是 nil,等号右边根本不会执行求值,赋值失败,返回 nil。
1-4 通过可选链取下标
可选链也能用在下标上,但问号的位置有讲究:问号紧跟在”那个可选的东西”后面、下标括号之前。
if let firstRoomName = john.residence?[0].name {
print("第一个房间叫 \(firstRoomName)")
}
这里 ?. 紧跟 john.residence,因为它是可选值。如果 residence 是 nil,整条取下标失败。反过来,如果是”返回可选的下标”(比如字典按 key 取值返回 T?),问号要放在下标括号之后,去链那个下标返回的可选值:
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91 // 成功
testScores["Brian"]?[0] = 72 // 失败,没有 Brian 这个键,整条返回 nil
1-5 多级可选链
你还可以把多层的可选一个接一个链起来,深入复杂的对象模型。比如 residence 是可选,residence 里的 address 也是可选,可以这样链:
if let street = john.residence?.address?.street {
print("街道是 \(street)")
}
重点来了:多级链接不会让可选多套一层。不管链多长,最终返回的可选类型层级,只取决于你要取的那个成员本身是不是可选。比如 street 是 String?,那么 john.residence?.address?.street 返回的还是 String?,不会因为链了两级就变成 String??。
Tip记不住问号放哪?记住一句:问号永远紧跟在”当前这个可选值”后面。是
residence可选,问号就紧贴residence;是方法返回值可选,问号就紧贴方法括号后面。
1-6 复合链与方法返回值的链接
可选链还有个很实用的特性:它和方法返回的可选值之间可以继续接下去,形成”方法返回可选,再对它可选链”的复合。比如前面 address 有个返回 String? 的方法 buildingIdentifier(),你可以先 ?.address?.buildingIdentifier() 取到那个可选字符串,再在它后面继续 ?.hasPrefix("The"),问号照样紧贴在”这个可选返回值”后面。链可以拉得很长,但每一环是否为 nil 都不影响整体优雅返回。
需要特别留心问号的位置,这是初学者高频出错点。规则再说一遍:问号永远紧贴”当前正在被可选链的那个可选值”。如果是属性或方法所属的对象是可选,问号贴它;如果是”下标或方法返回的结果”是可选,问号贴在下标括号或方法括号之后。把问号放错位置,要么编译报错,要么链不到你想链的那一层,取出来的值类型也不对。
可选链和强制解包最大的区别,是它对”缺失”的态度:强制解包把缺失当成崩溃,可选链把缺失当成正常的 nil 结果。所以只要存在”可能没值”的环节,就该默认用可选链,而不是图快写 !。配合 if let 取出链的结果、或 ?? 给默认值,就能既安全又干净地完成”深入一个可能为 nil 的对象图”这类任务。
1-7 易错点速记
可选链的问号位置是高频出错点,请刻进肌肉记忆:问号永远紧贴”当前这个可选值”。是属性所属对象可选,问号贴对象;是下标或方法返回的可选,问号贴在下标括号或方法括号后面。
结果永远可选:不管你链上取的是不是可选成员,经过 ?. 之后拿到的一定是可选,所以通常还要配 if let 或 ?? 进一步处理。多级链接不会让可选”套更多层”——String? 链再长还是 String?,不会变成 String??。
和强制解包对照着记:强制解包把”缺失”当崩溃,可选链把”缺失”当正常的 nil。只要存在可能没值的环节,默认用可选链,别图快写 !。实际写代码时,可选链、if let、?? 三者常常一起出现:可选链去取值,if let 接住结果,或 ?? 给默认值。组合熟练后,处理深层可能为 nil 的对象图会既安全又干净。
记住一句话就能判断用不用可选链:只要访问路径上有可能为 nil 的环节,就别用 !,改用 ?.。把”可能缺失”当成常态去设计,代码才稳。
1-8 小结
可选链用 ?. 在可选值上安全地点成员,任何一环为 nil 就整体返回 nil,绝不崩溃。它返回的结果永远可选,所以通常和 if let、?? 搭配。相比强制解包,可选链是日常访问可选值内部的首选武器。下一章的 ?? 运算符正是和它天生一对。