1️⃣ 值语义的优势和成本
优势:
- 安全:不会意外被其他对象修改
- 可预测:状态总是清晰、易于回溯(快照)
- 线程安全:读多写少不需要锁
成本:
- 频繁拷贝:大型 struct 或数组每次修改可能触发 CoW
- 额外内存分配:尤其数组、字典、字符串等 CoW buffer
所以核心矛盾:
你想保持值语义,但不想每次修改都触发昂贵的拷贝。
2️⃣ 高频修改下的常见优化策略
(1)分离“轻量状态”和“大块数据”
- 将状态拆成小 struct + 大数组/缓冲区:
struct UIState {
var counter: Int // 小值,频繁修改
var buffer: [Int] // 大数组,少量修改
}
- 轻量字段保留值语义,修改直接在栈上
- 大数组放在 heap 上,利用 CoW,只有写入时才复制
(2)局部可变引用(inout / class 包装)
- Swift struct 值语义和
inout或 class 包装 可以权衡性能:
class UIModel {
var state: UIState
init(_ state: UIState) { self.state = state }
}
// 更新状态
model.state.counter += 1
- 这样频繁修改不会触发 struct 拷贝
- 缺点:失去纯值语义,需要小心共享副作用
(3)只在边界复制(Copy-on-Write)
- 利用 CoW 容器 和 惰性复制:
struct BigUIState {
var data: [Int] // CoW
}
var state1 = BigUIState(data: Array(repeating: 0, count: 1_000_000))
var state2 = state1 // 不会立即复制
state2.data[0] = 1 // 第一次写触发 CoW
- 优化思路:频繁修改的字段单独拆出来,减少 CoW 触发次数
(4)批量更新而非单次更新
- 高频修改时,不要每次修改都触发状态更新:
var state = uiState
for i in 0..<1000 {
state.counter += i // 单次修改在本地变量
}
uiState = state // 批量赋值一次
- SwiftUI 或 Redux 风格架构也常用这种策略
(5)利用 struct + reference type 混合模型
- 典型设计:
struct UIState {
var counter: Int
var data: SharedData // class,heap 引用
}
class SharedData {
var array: [Int]
}
- 小字段保持值语义
- 大字段使用引用类型共享,性能稳定
- CoW buffer 仍可优化少量写入
3️⃣ 权衡原则总结
| 关注点 | 建议策略 |
|---|---|
| 高频修改小字段 | 保持值语义,直接修改即可 |
| 高频修改大数组/缓冲区 | class 包装 + CoW 或 inout |
| 多线程/快照需求 | 保持值语义,批量修改,避免实时拷贝大数组 |
| UI 框架触发渲染 | 批量修改 + 发布更新,避免每次写触发渲染 |
💡 核心理念:
值语义用于状态安全、可追踪;引用用于高频修改性能;两者组合,按需分离字段,批量更新