工具的使用决定了工程的高度。Instruments 是 Apple 官方的性能分析套件,是优化 App 性能的必备技能。
1. 启动 Instruments
从 Xcode 启动
- Product → Profile (⌘I)
- 选择目标设备和模板
- 点击红色录制按钮开始分析
命令行启动
# 打开 Instruments
open -a Instruments
# 直接运行指定模板
xcrun instruments -t "Time Profiler" -D output.trace MyApp.app
2. Time Profiler - CPU 性能分析
定位 CPU 耗时函数,解决卡顿问题。
使用方法
- 选择 Time Profiler 模板
- 录制 App 操作
- 查看 Call Tree(调用树)
关键设置
在 Call Tree 面板底部:
- ✅ Separate by Thread: 按线程分离
- ✅ Invert Call Tree: 显示最耗时的函数在顶部
- ✅ Hide System Libraries: 隐藏系统库,聚焦自己的代码
- ✅ Flatten Recursion: 合并递归调用
分析技巧
Weight Self Weight Symbol Name
─────────────────────────────────────
50.0ms 10.0ms -[MyClass processData:]
30.0ms 5.0ms -[Parser parse:]
25.0ms 25.0ms JSONDecoder.decode
- Weight: 函数及其子函数的总耗时
- Self Weight: 函数自身耗时(不含子函数)
常见问题定位
// ❌ 主线程执行耗时操作
func viewDidLoad() {
let data = loadLargeFile() // 阻塞 UI
processData(data)
}
// ✅ 移到后台线程
func viewDidLoad() {
Task.detached {
let data = await loadLargeFile()
await MainActor.run {
self.processData(data)
}
}
}
3. Allocations - 内存分配分析
追踪内存分配,发现内存暴涨问题。
关键指标
| 指标 | 说明 |
|---|---|
| All Heap Allocations | 所有堆内存分配 |
| Persistent | 当前存活的对象 |
| Transient | 已释放的对象 |
| Total Bytes | 总分配字节数 |
Mark Generation
追踪特定时间段的内存增长:
- 点击 Mark Generation 按钮
- 执行操作(如进入/退出页面)
- 再次点击 Mark Generation
- 查看两个标记之间的内存增长
分析内存暴涨
// ❌ 内存暴涨:一次性加载大数组
func loadAllImages() -> [UIImage] {
return urls.map { UIImage(contentsOfFile: $0)! }
}
// ✅ 使用懒加载
func loadImage(at index: Int) -> UIImage? {
UIImage(contentsOfFile: urls[index])
}
4. Leaks - 内存泄漏检测
自动检测循环引用导致的内存泄漏。
工作原理
Leaks 工具会:
- 扫描堆内存中的所有对象
- 检查对象的引用关系
- 找出无法从根对象到达但仍存在的对象
常见泄漏场景
// 1. 闭包循环引用
class ViewController: UIViewController {
var handler: (() -> Void)?
func setup() {
handler = {
self.doSomething() // ❌ 泄漏
}
handler = { [weak self] in
self?.doSomething() // ✅ 修复
}
}
}
// 2. Timer 循环引用
timer = Timer.scheduledTimer(
timeInterval: 1,
target: self, // ❌ 强引用
selector: #selector(tick),
userInfo: nil,
repeats: true
)
// 3. Delegate 循环引用
class Child {
var delegate: ParentDelegate? // ❌ 应该是 weak
}
5. Network - 网络分析
分析 App 的网络请求。
关键信息
- 请求 URL 和方法
- 请求/响应时间
- 数据大小
- 状态码
优化建议
// 1. 使用缓存
let config = URLSessionConfiguration.default
config.requestCachePolicy = .returnCacheDataElseLoad
// 2. 压缩数据
request.setValue("gzip", forHTTPHeaderField: "Accept-Encoding")
// 3. 合并请求
// 避免多个小请求,使用批量 API
6. Core Animation - 渲染性能
分析 UI 渲染性能,定位掉帧原因。
关键指标
| 指标 | 说明 | 理想值 |
|---|---|---|
| FPS | 帧率 | 60 FPS |
| GPU Utilization | GPU 使用率 | < 70% |
调试选项
在 Simulator 的 Debug 菜单:
- Color Blended Layers: 显示图层混合(红色区域需优化)
- Color Offscreen-Rendered: 离屏渲染(黄色区域)
- Color Misaligned Images: 图片尺寸不匹配
常见渲染问题
// ❌ 导致离屏渲染
view.layer.cornerRadius = 10
view.layer.masksToBounds = true
view.layer.shadowColor = UIColor.black.cgColor
// ✅ 优化:使用贝塞尔路径
let path = UIBezierPath(roundedRect: view.bounds, cornerRadius: 10)
let mask = CAShapeLayer()
mask.path = path.cgPath
view.layer.mask = mask
// ✅ 或使用 cornerCurve (iOS 13+)
view.layer.cornerRadius = 10
view.layer.cornerCurve = .continuous
7. Energy Log - 能耗分析
分析 App 的电量消耗。
能耗来源
| 来源 | 影响 |
|---|---|
| CPU | 高 |
| GPU | 高 |
| 网络 | 中 |
| 定位 | 高 |
| 蓝牙 | 中 |
优化建议
// 1. 减少定位精度
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
// 2. 使用 Significant Location Changes
locationManager.startMonitoringSignificantLocationChanges()
// 3. 合理使用后台任务
BGTaskScheduler.shared.register(forTaskWithIdentifier: "refresh") { task in
// 高效完成任务
task.setTaskCompleted(success: true)
}
8. System Trace - 系统级分析
深入分析线程调度、系统调用等底层行为。
适用场景
- 分析线程阻塞
- 定位锁竞争
- 理解系统调用
常见问题
// ❌ 锁竞争
let lock = NSLock()
func process() {
lock.lock()
// 长时间持有锁
heavyComputation()
lock.unlock()
}
// ✅ 减少锁持有时间
func process() {
let data = prepareData() // 锁外准备
lock.lock()
quickUpdate(data) // 快速更新
lock.unlock()
}
9. App Launch - 启动时间分析
专门分析 App 启动性能。
启动阶段
┌─────────────────────────────────────────────┐
│ App Launch │
├─────────────────────────────────────────────┤
│ Process Creation │ dyld │ Runtime Init │
├─────────────────────────────────────────────┤
│ UIKit Init │ Application Init │ First Frame │
└─────────────────────────────────────────────┘
优化目标
| 启动类型 | 目标时间 |
|---|---|
| 冷启动 | < 400ms |
| 热启动 | < 200ms |
| 恢复 | < 100ms |
优化策略
// 1. 延迟非必要初始化
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// ✅ 只做必要的初始化
setupCriticalServices()
// ✅ 延迟非必要初始化
DispatchQueue.main.async {
self.setupAnalytics()
self.setupPushNotifications()
}
return true
}
}
// 2. 使用 +initialize 替代 +load
// 3. 减少动态库数量
// 4. 使用 Assets Catalog 优化图片加载
10. 自定义 Instrument
使用 os_signpost 添加自定义埋点。
import os.signpost
let log = OSLog(subsystem: "com.myapp", category: "Performance")
func processData(_ data: Data) {
let signpostID = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "Process Data", signpostID: signpostID)
// 实际处理
let result = parse(data)
os_signpost(.end, log: log, name: "Process Data", signpostID: signpostID)
}
在 Instruments 中选择 os_signpost 模板即可查看。
11. 快捷键
| 快捷键 | 功能 |
|---|---|
| ⌘R | 开始/停止录制 |
| ⌘1-9 | 切换面板 |
| ⌥拖动 | 缩放时间轴 |
| ⌘F | 搜索 |
| ⌘E | 导出数据 |
12. 最佳实践
分析流程
- 确定问题:卡顿?内存?耗电?
- 选择工具:Time Profiler / Allocations / Energy
- 录制场景:复现问题的操作
- 分析数据:找到热点函数/对象
- 优化代码:针对性修复
- 验证效果:再次录制对比
注意事项
- 使用 Release 配置分析(Debug 有额外开销)
- 在真机上测试(模拟器性能不同)
- 多次测试取平均值
- 保存
.trace文件便于对比