一、先建立分析心智模型(非常关键)
Swift 中只有两类“慢源头”
-
值类型路径
- struct / enum
- 问题来源:复制、stride、COW 失效
-
引用类型路径
- class / closure
- 问题来源:retain / release 抖动、逃逸、堆分配
👉 所有性能问题,都能归到这两类之一。
二、第一步:用“内存模型”快速定性(不用工具)
1️⃣ 看到 struct,就问 4 个问题
struct BigModel {
var a: Int
var b: [Int]
}
问自己:
- size / stride 多大? (> 64B 警惕)
- 是否频繁赋值 / 传参 / 返回?
- 是否频繁写?(会触发 COW)
- 是否在循环 / map / async 中?
如果 ≥2 个是 “是” → 复制热点
2️⃣ 看到 class,就问 4 个问题
class Node {
var next: Node?
}
问自己:
- 是否频繁创建 / 销毁?
- 是否被 closure / async 捕获?
- 是否通过 protocol / AnyObject 传递?
- 是否跨线程?
如果 ≥2 个是 “是” → ARC 压力热点
三、第二步:用 Instruments 定位“哪一种压力”
1️⃣ 查 ARC 压力(引用类型)
用 Instruments → Allocations + Zombies(调试)
关注:
retainreleaseobjc_retainswift_retain
如果看到:
- 高频 retain/release
- 对象生命周期极短
👉 ARC 抖动
2️⃣ 查值类型复制(struct / enum)
用 Instruments → Time Profiler
看:
swift_retain+swift_release(COW buffer)memcpyoutlined copycopy_value_buffer
👉 命中这些,基本就是 值复制
四、第三步:回到 Swift 内存模型做“根因分析”
场景 A:struct 复制热点
models.map {
var m = $0
m.count += 1
return m
}
内存模型视角:
- 每个
$0是一个 完整值拷贝 map生成新数组- 每次写触发 COW
👉 N × size 的复制
修复策略:
for i in indicesinout- 拆 struct
- 手动 COW
场景 B:ARC 抖动热点
for _ in 0..<10_000 {
let obj = Foo()
use(obj)
}
内存模型视角:
-
每次 loop:
- heap alloc
- retain
- release
-
逃逸分析失败(use 不透明)
修复策略:
- 对象池
- 值类型替代
final class- inline / 泛型
五、第四步:判断“该用哪种手段解决”
| 现象 | 根因 | 解决方向 |
|---|---|---|
| memcpy 多 | struct 太大 | 拆分 / 引用内核 |
| swift_retain 多 | COW buffer | 减少写 |
| objc_retain 多 | class 逃逸 | final / 局部化 |
| alloc 多 | 堆频繁 | 栈化 / 复用 |
六、一个真实“翻车 → 修复”的例子
❌ 原始代码
struct State {
var list: [Item]
}
func update(_ state: State) -> State {
var s = state
s.list.append(Item())
return s
}
State在 reducer 中被疯狂复制- 每次 append 都触发 COW
✅ 修复后
func update(_ state: inout State) {
state.list.append(Item())
}
- 单一 buffer
- 无中间复制
- ARC / memcpy 大幅下降
七、进阶:SIL 视角(面试加分)
如果你说一句:
“我会看 SIL 里是否有
copy_value/retain_value,
以及alloc_ref是否被提升为alloc_stack”
面试官基本就知道你不是在背书。
八、终极总结(工程可落地)
用 Swift 内存模型分析性能问题的关键,是先区分“值复制”还是“ARC 压力”,
再从 size / stride / COW / 逃逸分析四个维度定位根因,
最后选择结构性改造,而不是微调语法。