在 Swift 的设计哲学中,let(不可变)与 var(可变)的区分不仅仅是语法层面的偏好,它是构建健壮架构和高性能多线程应用的基础。
通过强制区分不可变性,Swift 将许多运行时的潜在错误提前到了编译时解决。
1. 多线程中的核心优势:消除竞态条件
在多线程环境下,最危险的操作是**“多个线程同时修改同一个内存地址”**(Data Race)。
- 线程安全(Thread Safety) :
let定义的值是天生线程安全的。因为不可变对象一旦初始化就无法更改,多个线程可以同时读取它而无需加锁(Lock)。 - 消除锁开销:使用
var时,为了保证一致性,通常需要使用NSLock、DispatchQueue或actors来同步访问。频繁的加锁和解锁会带来巨大的上下文切换开销。使用let可以完全避免这些开销,从而提高并发性能。 - 内存模型预测:编译器知道
let的值不会改变,因此可以将该值缓存在寄存器中,而不需要每次都从内存中重新读取,这在高性能计算中至关重要。
2. 架构设计中的优势:可预测性 (Predictability)
现代 Swift 架构(如 SwiftUI 和 Composable Architecture)重度依赖不可变性来简化逻辑。
A. 引用透明性 (Referential Transparency)
当你的数据模型主要由 struct 和 let 组成时,函数就变成了“纯函数”。
- 易于测试:输入相同的模型,输出永远相同。你不需要担心某个后台线程在测试运行期间偷偷修改了模型的状态。
- 降低认知负荷:当你阅读代码看到
let user = ...时,你可以确信在当前作用域的余下部分,user的状态是恒定的。
B. 防御性拷贝的终结
在 Objective-C 中,为了防止外部修改自己的属性,我们经常需要写 copy 关键字。
- Swift 的优势:值类型(Struct)结合
let实现了自动的写时复制(Copy-on-Write)。这确保了每个模块持有的数据副本是安全的,不会被其他模块意外修改,从而解耦了模块间的依赖。
3. 在不同架构模式中的实践
| 架构组件 | 推荐使用 | 理由 |
|---|---|---|
| 数据模型 (Models) | let | 确保从网络解析后的数据在流转过程中不被篡改。 |
| 视图状态 (ViewState) | let | SwiftUI 通过对比不可变的 State 镜像来决定是否需要重新渲染 UI。 |
| 业务逻辑 (Logic) | let | 减少中间变量,利用 map/filter 产生新的不可变结果。 |
4. 编译器优化视角
使用 let 还能给编译器(LLVM)提供优化空间:
- 常量折叠(Constant Folding) :编译器可以在编译期计算出结果,直接替换为常量。
- 逃逸分析(Escape Analysis) :如果一个
let闭包没有逃逸,编译器可以将其优化为内联执行,显著提升执行效率。
总结:如何选择?
- 默认使用
let:这是 Swift 社区的黄金法则。只有当你确定需要修改某个值,且这种修改在逻辑上是有意义的时,才将其改为var。 - 局部化可变性:如果一个复杂的计算过程需要多次修改变量,可以在函数内部使用
var,但返回一个let的结果。这被称为“内部可变,外部不可变”。