5-8.【性能分析与优化】对于高频修改的模型对象(如 UI state),你如何在值语义和性能之间做权衡?

2 阅读2分钟

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 框架触发渲染批量修改 + 发布更新,避免每次写触发渲染

💡 核心理念:

值语义用于状态安全、可追踪;引用用于高频修改性能;两者组合,按需分离字段,批量更新