第9章 可选值:Swift 安全感的源头

第九章:可选值:Swift 安全感的源头

几乎所有语言都被“这个值可能不存在”折磨过。Swift 选择把这种可能性写进类型系统:Int 和 Int? 是两个不同的类型。编译器不会让你忘记处理“没有值”的情况,这就是 Swift 让人安心的核心原因之一。

9.1 nil 不是空对象,而是“没有值”

只有可选类型才能赋值为 nil:

1
2
3
4
5
6
7
var nickname: String? = "Kai"
print(nickname as Any)
// prints: Optional("Kai")

nickname = nil
print(nickname as Any)
// prints: nil

下面的写法不能通过编译,因为 String 类型的变量不允许缺席:

1
error: 'nil' cannot initialize specified type 'String'

问号紧贴类型,不是紧贴变量名。两种写法等价:

1
2
3
4
var a: Int? = nil
var b: Optional<Int> = nil
print(a as Any, b as Any)
// prints: nil nil

Int? 只是 Optional<Int> 的语法糖。完整定义会在第 16 章揭开:可选值本质上是一个枚举,有 .some(value) 和 .none 两个分支。现在只要先记住:可选值是“盒子”,里面要么装着一个值,要么什么都没有。

9.2 读取可选值:先证明它存在

直接拿 String? 去调用 String 的方法会失败:

1
error: value of optional type 'String?' must be unwrapped

最小心的做法是可选绑定:

1
2
3
4
5
6
if let name = nickname {
    print("Hello, \(name)")
} else {
    print("还没有昵称")
}
// prints: 还没有昵称

常用的简写方式省掉重复的名字:

1
2
3
4
5
nickname = "Kai"
if let nickname {
    print(nickname.uppercased())
    // prints: KAI
}

这种省掉重复名字的绑定简写不只用在 if let 上,guard let、while case let x? 都有对应形式,统一整理在第 15B.8 节。

可选绑定还能同时检查多个值,任意一个为 nil 就进入 else:

1
2
3
4
5
6
7
let first: String? = "A"
let second: String? = "B"

if let first, let second {
    print(first + second)
    // prints: AB
}

也可以用 , 追加布尔条件:

1
2
3
4
5
let age: Int? = 20
if let age, age >= 18 {
    print("成年:\(age)")
    // prints: 成年:20
}

9.3 guard let:提前退场,主干更清爽

当函数必须在参数缺失时立刻返回,guard 比层层嵌套的 if 更合适:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func greet(_ name: String?) {
    guard let name, !name.isEmpty else {
        print("名字无效")
        return
    }
    print("你好,\(name)")
}

greet(nil)
// prints: 名字无效
greet("Mia")
// prints: 你好,Mia

guard 的硬规则:else 块必须离开当前作用域,可以 return、break、continue 或 throw。绑定成功的值在 guard 之后的整个作用域都能用,这也是它比 if let 更适合参数校验的原因。

9.4 空合运算符:给缺席的值准备替补

?? 在左边有值时取左边,否则取右边:

1
2
3
4
5
6
7
let input: String? = nil
print(input ?? "默认值")
// prints: 默认值

let saved: String? = "已保存"
print(saved ?? "默认值")
// prints: 已保存

右边可以是另一个可选值,于是能连续兜底:

1
2
3
4
5
let a: Int? = nil
let b: Int? = nil
let c: Int? = 3
print(a ?? b ?? c ?? 0)
// prints: 3

?? 会短路:左边一旦有值,右边不会求值。

1
2
3
4
5
6
7
8
func fallback() -> Int {
    print("fallback 被调用")
    return 0
}

let value: Int? = 10
print(value ?? fallback())
// prints: 10

9.5 可选链:一路问号,一路安全

在可能为 nil 的属性、方法或下标后面写 ?,就形成可选链。链中任何一环断裂,整个表达式返回 nil,不会崩溃:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct Address {
    var city: String
}

struct User {
    var name: String
    var address: Address?
}

let user = User(name: "Lin", address: nil)
print(user.address?.city as Any)
// prints: nil
print(user.address?.city ?? "未填写城市")
// prints: 未填写城市

可选链的方法调用也能返回可选值:

1
2
3
let upper = user.address?.city.uppercased()
print(upper as Any)
// prints: nil

注意 .city 后面不必再写问号:可选链一旦开始,后续访问都会继承“可能失败”的性质。

9.6 map 与 flatMap:在盒子里做变换

可选值也有 map:有值时对内容变换,没值时原样保持 nil:

1
2
3
4
5
6
7
let count: Int? = 3
print(count.map { $0 * 2 } as Any)
// prints: Optional(6)

let empty: Int? = nil
print(empty.map { $0 * 2 } as Any)
// prints: nil

如果变换本身又返回可选值,map 会得到嵌套可选值,flatMap 则会压平:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func parseEven(_ text: String) -> Int? {
    guard let value = Int(text), value.isMultiple(of: 2) else { return nil }
    return value
}

let text: String? = "42"
print(text.map(parseEven) as Any)
// prints: Optional(Optional(42))
print(text.flatMap(parseEven) as Any)
// prints: Optional(42)

9.7 强制解包与隐式解包:能不用就别用

! 表示“我确定盒子里一定有值”:

1
2
3
let definite: Int? = 7
print(definite!)
// prints: 7

一旦判断错误,程序立即崩溃:

1
Fatal error: Unexpectedly found nil while unwrapping an Optional value

try! 和 as! 也是同一类危险动作。它们不是禁止使用,而是应该稀有。

隐式解包可选类型写作 Type!,常用于“初始化时暂时为空,之后一定会有值”的场景:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class View {
    var title: String!

    func render() {
        print(title)
        print(title.count)
        let explicit: String = title
        print(explicit)
    }
}

let view = View()
view.title = "首页"
view.render()
// prints: Optional("首页")
// prints: 2
// prints: 首页

第一行输出会让很多人愣住:print(title) 打出来的是 Optional("首页"),编译器还会顺带警告 coercion of implicitly unwrappable value of type 'String?' to 'Any' does not unwrap optional。原因在于 print 的参数类型是 Any,不属于“需要具体类型”的位置,隐式解包在这里不触发。

真正需要 String 的地方才会自动解包:title.count 能用,let explicit: String = title 也能用。想安心打印,就写成 print(title!) 或 print(title ?? "")。

不管怎样,它仍然是可选类型,只是编译器允许你省略 !。在现代 Swift 代码里,能用正常可选值和构造器解决的,就不要依赖隐式解包。

9.8 嵌套可选与可选模式

可选值可以嵌套,虽然日常少见:

1
2
3
let nested: Int?? = 5
print(nested as Any)
// prints: Optional(Optional(5))

switch 能直接匹配可选值:

1
2
3
4
5
6
7
8
let maybe: Int? = 42
switch maybe {
case .some(let value):
    print("有值:\(value)")
    // prints: 有值:42
case .none:
    print("没有值")
}

更常见的是可选值模式 .some 的简写:

1
2
3
4
if case let value? = maybe {
    print(value)
    // prints: 42
}

9.9 本章小结

写法含义
T?可能没有值的 T
if let有值才进入分支,适合局部处理
guard let没值就提前离开,适合参数校验
??提供默认值,且会短路
?.可选链,任一环缺失就返回 nil
map / flatMap在可选值内部做变换
!强制解包,失败即崩溃
T!隐式解包可选值,仍可能崩溃

9.10 本章易错点速查

易错点正确理解
认为 Int? 和 Int 可以随意混用它们是不同类型,必须先解包
用 ?? 代替所有可选绑定需要处理失败分支时,if let/guard let 更清楚
guard let 的 else 里不退出编译器会强制你 return、throw 或跳转
在可选链里继续写很多 ?开头一个 ? 之后,后续访问自动纳入链条
map 里又返回可选值会产生嵌套可选;需要压平时用 flatMap
到处使用 !每个 ! 都是一张可能兑现的崩溃彩票
把 nil 当作 0、空字符串nil 表示没有值,不是某种默认值

9.11 下章预告

下一章进入集合:Array、Dictionary 和 Set。它们看似普通,却会把你第一次带到 Swift 最核心的设计思想之一——值语义:你复制的是一个集合,不是指向同一个集合的遥控器。

最后修改 September 19, 2026: 更新 (3489033b1)