Swift 基础语法全景(二):可选型、解包与内存安全

23 阅读3分钟

为什么需要 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 大场景

  1. 可失败构造器
let str = "123"
let num = Int(str)   // 返回 Int?,因为 "abc" 无法转数字
  1. 字典下标
let dict = ["a": 1]
let value = dict["b"] // Int?,键缺失时为 nil
  1. 反射 & KVO
  2. 异步回调“结果/错误”
  3. 链式访问可能中断
  4. 服务端 JSON 解析字段缺失

解包 4 件套

方式语法适用场景风险
强制解包 !optional!100% 确定有值运行时崩溃
可选绑定 if letif let x = optional { }临时只读常量
守护式 guard letguard 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 在编译期强制以下规则:

  1. 变量使用前必须初始化(Definite Initialization)
let x: Int
print(x)   // ❌ 报错:使用前未初始化
  1. 数组越界立即崩溃
let arr = [1, 2, 3]
let v = arr[5]   // 运行期崩溃,而非缓冲区溢出
  1. 对象释放后无法访问(ARC + 强引用)
  2. 并发访问冲突检测(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

  1. 永远优先用 if let / guard let 而非 !
  2. 对外暴露的 API 返回 Optional,表明“可能失败”
  3. 隐式解包 只留给 @IBOutlet 或立即初始化的常量
  4. 可选链 + nil-coalescing 可让代码保持“一行表达”
  5. 编译期内存安全四件套让 C 式野指针错误几乎绝迹