为什么需要 Optional?—— 把“没有值”做成类型
Objective-C 用 nil
指针表示“无”,但运行时才发现野指针;
Swift 把“可能有值 / 可能没有”编译期就写进类型系统,消灭空指针异常。
// 错误:普通 Int 永远不能为 nil
var age: Int = nil // ❌ Compile-time error
// 正确:可选型才能表达“缺值”
var age: Int? = nil // ✅
Optional 的本质——语法糖背后的 enum
// 伪代码,真实定义在标准库
enum Optional<Wrapped> {
case none // 无值
case some(Wrapped) // 有值
}
因此 Int?
只是 Optional<Int>
的简写。
你可以手动拼出 Optional:
let x: Optional<Int> = .some(5)
let y: Int? = .none
产生 Optional 的 6 大场景
- 可失败构造器
let str = "123"
let num = Int(str) // 返回 Int?,因为 "abc" 无法转数字
- 字典下标
let dict = ["a": 1]
let value = dict["b"] // Int?,键缺失时为 nil
- 反射 & KVO
- 异步回调“结果/错误”
- 链式访问可能中断
- 服务端 JSON 解析字段缺失
解包 4 件套
方式 | 语法 | 适用场景 | 风险 |
---|---|---|---|
强制解包 ! | optional! | 100% 确定有值 | 运行时崩溃 |
可选绑定 if let | if let x = optional { } | 临时只读常量 | 无 |
守护式 guard let | guard let x = optional else { return } | 提前退出(如函数/作用域中) | 无 |
nil-coalescing ?? | optional ?? default | 提供兜底值 | 无 |
代码对比:
// 1. 强制解包—— Demo 可用,生产禁止
let serverPort = Int("8080")! // 若配置写错直接崩溃
// 2. if let—— 最常用
if let port = Int("8080") {
print("绑定端口:\(port)")
}
// 3. guard let—— 函数早期退出,减少嵌套
func connect(host: String, portText: String) -> Bool {
guard let port = Int(portText) else { return false }
// port 从这一行开始是非可选
return true
}
// 4. ?? —— 给默认值,代码最短
let port = Int("8080") ?? 80
可选链 Optional Chaining—— 一句话 安全穿透
class Address {
var street: String?
}
class Person {
var address: Address?
}
let bob: Person? = Person()
let streetLength = bob?.address?.street?.count ?? 0
// 任意环节 nil 立即返回 nil,不崩
隐式解包 Optional(IUO===implicit unwrap optional)—— 99% 的场景你不需要
语法:类型后加 !
而非 ?
var name: String! // 隐式解包
print(name.count) // 编译器帮你偷偷加 `!`
看似方便,实际埋雷:
- 值后来变成 nil → 运行时崩
- 与 Objective-C 接口对接时,系统 API 可能标记为 IUO,仍需手动判空
官方建议:只在“初始化后立刻有值,且之后不会 nil” 使用,例如 Storyboard IBOutlet。
其他场景用普通 Optional + guard let
最稳。
内存安全四件套——编译期即消灭悬垂指针
Swift 在编译期强制以下规则:
- 变量使用前必须初始化(Definite Initialization)
let x: Int
print(x) // ❌ 报错:使用前未初始化
- 数组越界立即崩溃
let arr = [1, 2, 3]
let v = arr[5] // 运行期崩溃,而非缓冲区溢出
- 对象释放后无法访问(ARC + 强引用)
- 并发访问冲突检测(Swift 5.5+ Actor & Sendable)
Optional 实战:解析 JSON 字段
struct User: Decodable {
let id: Int
let name: String
let avatar: URL? // 用户可能没上传头像
}
let json = """
{"id": 1, "name": "Alice"}
""".data(using: .utf8)!
do {
let user = try JSONDecoder().decode(User.self, from: json)
let url = user.avatar?.absoluteString ?? "default.png"
print(url)
} catch {
print(error)
}
利用 Optional 天然表达“字段缺失”,无需写大量 if xxx != NSNull
判断。
性能Tips:Optional 会多占内存吗?
- Optional 底层会多 1 个 byte 存放“是否有值”标记,对齐后几乎无感知。
- 在值类型栈空间,编译器会做内联优化,不用担心“装箱”开销。
- 高频率调用处(如 3D 顶点数据)可用
UnsafeBufferPointer
避开 Optional。
小结 & checklist
- 永远优先用
if let / guard let
而非!
- 对外暴露的 API 返回 Optional,表明“可能失败”
- 隐式解包 只留给 @IBOutlet 或立即初始化的常量
- 可选链 + nil-coalescing 可让代码保持“一行表达”
- 编译期内存安全四件套让 C 式野指针错误几乎绝迹