Instruments 调优

性能分析实战。使用 Time Profiler 定位卡顿,Allocations 监控内存峰值,打造极致丝滑体验。

工具的使用决定了工程的高度。Instruments 是 Apple 官方的性能分析套件,是优化 App 性能的必备技能。


1. 启动 Instruments

从 Xcode 启动

  1. Product → Profile (⌘I)
  2. 选择目标设备和模板
  3. 点击红色录制按钮开始分析

命令行启动

# 打开 Instruments
open -a Instruments

# 直接运行指定模板
xcrun instruments -t "Time Profiler" -D output.trace MyApp.app

2. Time Profiler - CPU 性能分析

定位 CPU 耗时函数,解决卡顿问题。

使用方法

  1. 选择 Time Profiler 模板
  2. 录制 App 操作
  3. 查看 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

追踪特定时间段的内存增长:

  1. 点击 Mark Generation 按钮
  2. 执行操作(如进入/退出页面)
  3. 再次点击 Mark Generation
  4. 查看两个标记之间的内存增长

分析内存暴涨

// ❌ 内存暴涨:一次性加载大数组
func loadAllImages() -> [UIImage] {
    return urls.map { UIImage(contentsOfFile: $0)! }
}

// ✅ 使用懒加载
func loadImage(at index: Int) -> UIImage? {
    UIImage(contentsOfFile: urls[index])
}

4. Leaks - 内存泄漏检测

自动检测循环引用导致的内存泄漏。

工作原理

Leaks 工具会:

  1. 扫描堆内存中的所有对象
  2. 检查对象的引用关系
  3. 找出无法从根对象到达但仍存在的对象

常见泄漏场景

// 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. 最佳实践

分析流程

  1. 确定问题:卡顿?内存?耗电?
  2. 选择工具:Time Profiler / Allocations / Energy
  3. 录制场景:复现问题的操作
  4. 分析数据:找到热点函数/对象
  5. 优化代码:针对性修复
  6. 验证效果:再次录制对比

注意事项

  • 使用 Release 配置分析(Debug 有额外开销)
  • 真机上测试(模拟器性能不同)
  • 多次测试取平均值
  • 保存 .trace 文件便于对比