首页 / Swift 编程语言教程 / 可选链 Optional Chaining

Swift 编程语言教程

可选链 Optional Chaining

本教程共 93 篇 · 第 42 篇 · 更新于 2026-08-08 · 约 8 分钟阅读

SwiftSwift 编程语言教程可选链Optional Chaining可选类型nil安全调用

本节目标:学会用 ?. 在可选值上安全地访问属性、调用方法、取下标,明白”任何一环为 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("设值成功")
}

如果 residencenil,等号右边根本不会执行求值,赋值失败,返回 nil

1-4 通过可选链取下标

可选链也能用在下标上,但问号的位置有讲究:问号紧跟在”那个可选的东西”后面、下标括号之前

if let firstRoomName = john.residence?[0].name {
    print("第一个房间叫 \(firstRoomName)")
}

这里 ?. 紧跟 john.residence,因为它是可选值。如果 residencenil,整条取下标失败。反过来,如果是”返回可选的下标”(比如字典按 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)")
}

重点来了:多级链接不会让可选多套一层。不管链多长,最终返回的可选类型层级,只取决于你要取的那个成员本身是不是可选。比如 streetString?,那么 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?? 搭配。相比强制解包,可选链是日常访问可选值内部的首选武器。下一章的 ?? 运算符正是和它天生一对。