1️⃣ 现象
在 Time Profiler 中,你看到:
swift_retain
swift_release
swift_retain
swift_release
...
-
频繁调用 → CPU 占用高 → 热路径性能下降
-
通常出现在:
- class 实例频繁创建 / 传递
- 闭包捕获 / 临时对象
- 协议存在类型 / 泛型值类型逃逸到堆
- 数组 / 字典写操作触发 CoW 并逃逸到 heap
2️⃣ 分析思路
(1)确认对象类型
-
ARC 只对 class / heap 对象 / heap 逃逸 struct 生效
-
检查:
- 是否频繁创建临时 class
- 是否值类型(struct / enum)由于 逃逸 被放到 heap → 引用计数操作
(2)检查调用上下文
-
使用 Extended Detail → Call Tree,展开函数栈
-
找到
swift_retain/swift_release的调用者 -
常见热点:
- 协议存在类型调用(existential) → 值类型被 box → heap + ARC
- 泛型函数返回逃逸 struct → heap + ARC
- SwiftUI / Combine pipeline 中 closure 捕获 class
(3)结合 Allocations 工具
- 对应 Time Profiler 栈展开 → Allocations 查看对象数量
- 如果对象频繁分配 → retain/release 频繁
- 可以判断 是短生命周期对象频繁堆分配,还是 长生命周期对象频繁引用修改
3️⃣ 常见优化手段
(1)减少 heap 分配 / 值类型逃逸
struct BigStruct {
var data: [Int]
}
func process(_ x: BigStruct) { ... }
-
如果
x逃逸 → heap + ARC -
优化:
- 使用
inout传递,避免拷贝到 heap - 使用局部副本或批量操作
- 使用
func processInout(_ x: inout BigStruct) { ... }
(2)减少协议存在类型 boxing
-
[Protocol]/let p: Protocol = ...→ 值类型被 box → ARC -
优化:
- 泛型约束代替 protocol existential → 静态派发
-
示例:
protocol Shape { func area() -> Double }
func totalArea<T: Shape>(_ shapes: [T]) -> Double {
shapes.reduce(0) { $0 + $1.area() } // 静态派发 + 无 ARC boxing
}
(3)closure 捕获优化
let array = [1,2,3]
let doubled = array.map { $0 * 2 } // closure 捕获
-
小闭包捕获堆分配 class → retain/release
-
优化:
- 避免捕获大量对象
- 使用局部 struct 或泛型函数
(4)热点函数内联
- 小函数 →
@inline(__always)→ 编译器可优化 retain/release - 避免临时对象频繁堆分配
(5)分析 SwiftUI / Combine 情况
-
SwiftUI view struct 频繁更新 → heap escape + ARC
-
Combine pipeline sink / map / assign closure 捕获 class → retain/release
-
优化策略:
- 尽量用 struct / 泛型代替 class
- 避免 protocol existential / AnyPublisher / AnyObject 捕获
4️⃣ 总结分析流程
-
确认类型:class / heap struct / closure → ARC
-
确认调用点:Time Profiler 展开 call tree
-
配合 Allocations:判断对象频繁分配
-
诊断原因:
- 临时 class / heap 逃逸 struct
- 协议存在类型 boxing
- closure 捕获 class
-
优化方法:
- 泛型代替协议存在类型
- inout / 局部副本
- final class / struct 优化静态派发
- 内联 / 小函数减少 retain/release
核心理念:
频繁 retain/release = 对 heap 对象操作频繁 → 静态派发 + 值类型 + 泛型 + inout + 内联 → 可显著降低 ARC 开销