Profile, fix, and verify: Improve app responsiveness with Instruments
Swift & Data 进阶 26m

性能分析、修复和验证:利用 Instruments 提升 App 响应性

Profile, fix, and verify: Improve app responsiveness with Instruments

2026年6月10日

在 Apple 官方观看视频

一句话判断

Instruments 27 终于把“找卡顿”从玄学变成了填空题,新增的 Top Functions 视图和 Swift Executors 工具让新手也能一眼看穿 Main Actor 阻塞和同步 I/O 的锅。

这场 Session 讲了什么

以前查 App 卡顿,Time Profiler 的 Call Tree 简直像天书,遇到 CPU 闲置的卡顿(比如等锁、等文件读写)更是抓瞎。Swift Concurrency 普及后,Main Actor 被隐式占用导致的 UI 掉帧成了新灾区,老手凭直觉排查的效率也跟不上。

Instruments 27 直接给了一套四步诊断心智模型:先看 CPU 饱和度高不高,再看采样数据,接着查执行争用(Execution contention),最后排查系统阻塞(System blocking)。配合全新的 Top Functions 模式,最耗时的函数直接平铺在眼前,不用再一层层展开调用栈。

这对所有被 Swift Concurrency 隐式 Main Actor 坑过、或者被同步文件读写卡住主线程的开发者影响最大。你不需要再是性能优化专家,只要跟着这套工作流,就能精准定位是算法太慢、Actor 争用,还是底层 I/O 阻塞。

值得深挖的点

Swift Concurrency 下的 Main Actor 饱和与 @concurrent 逃逸

Apple 这次专门拿出了 Swift Executors instrument 来解决 Swift Concurrency 带来的新型卡顿。过去我们写 Task { ... },如果上下文在 Main Actor 里,这个 Task 默认也会在 Main Actor 执行。当你在这个 Task 里做稍微重一点的计算(比如解码画布数据、渲染缩略图),Main Actor 就会被塞满,UI 直接卡死。

Apple 的解法是在 Task 闭包上加上 @concurrent 属性。这个设计选择非常务实,它允许开发者在保持 Swift Concurrency 结构化并发的同时,显式地把特定计算任务“踢”出 Main Actor,扔到后台线程池去跑。和以前用 Task.detached 或者手动切 DispatchQueue.global() 相比,这种方式更语义化,也更容易被 Instruments 的 Executors 视图追踪。坑在于,如果你滥用这个属性,把需要更新 UI 的代码也扔进去了,就会触发跨 Actor 的数据竞争警告,甚至引发 UI 更新不在主线程的 Crash。

同步 I/O 导致的 System Blocking 与低 CPU 卡顿

这是很多新手最容易踩的坑:App 卡了,打开 Time Profiler 一看,CPU 使用率极低,Call Tree 里什么也看不出。这是因为 Time Profiler 只采样 CPU 活跃的周期,当主线程在等 Data.write(to:options:.atomic) 这种同步文件写入时,CPU 是闲着的。

Instruments 27 引入了 System Trace 和全新的 Inspector 面板来填补这个盲区。当你在 Time Profiler 里发现 CPU 闲置但 App 挂起时,切到 System Trace,Inspector 面板会直接告诉你主线程正在等待文件 I/O 或同步锁。Apple 强烈建议把这类操作包在后台 Swift task 里。这里的 trade-off 是,异步化文件写入会增加代码复杂度,你需要处理写入完成后的状态回调,但相比于主线程被文件系统抖动卡死导致掉帧,这点复杂度是完全值得的。

代码片段

1. 用 OSSignpost 标记自定义业务区间

场景:在 Time Profiler 的 Timeline 里高亮你的核心业务逻辑(如套索选择),方便快速过滤和定位卡顿区间。

import os.signpost

// 初始化 signposter,指定子系统为 App 名称,类别为兴趣点
let signposter = OSSignposter(subsystem: "Demo App", category: .pointsOfInterest)
var lassoIntervalState: OSSignpostIntervalState? = nil

func lassoSelectionUpdated() {
    // 开始追踪区间,Instruments 会自动在 track 中显示
    lassoIntervalState = signposter.beginInterval("Lasso Selection")
    // 更新画布选择...
}

func lassoSelectionEnded() {
    // 结束区间追踪
    signposter.endInterval("Lasso Selection", lassoIntervalState!)
}

坑:OSSignpostIntervalState 必须成对出现,如果 begin 了没有 end,Instruments 里的区间会一直延伸到 Trace 结束,导致数据完全错乱。

2. 将耗时渲染任务移出 Main Actor

场景:在 Swift Concurrency 中,避免隐式继承 Main Actor 上下文导致 UI 线程被重计算阻塞。

let drawingData = note.drawingData
let canvasImages = note.decodeCanvas()

// 使用 @concurrent 将渲染任务移出 Main Actor,避免 UI 卡顿
thumbnail = await Task(name: "Render Thumbnail") { @concurrent in
    await renderThumbnail(
        drawingData: drawingData, 
        canvasImages: canvasImages, 
        size: CGSize(width: 300, height: 240)
    )
}.value

坑:@concurrent 闭包内绝对不能直接访问或修改 Main Actor 隔离的属性,否则编译器会报错,或者在运行时引发数据竞争。

3. 避免主线程同步文件写入

场景:将耗时的同步文件 I/O 移到后台任务,防止主线程因等待磁盘操作而挂起。

let encoder = PropertyListEncoder()
encoder.outputFormat = .binary
guard let data = try? encoder.encode(snapshots) else { return }

// 标记文件写入区间,方便在 System Trace 中观察 I/O 阻塞
let id = signposter.beginInterval("Writing To File")

// 将同步写入操作放到后台 Task 中执行,释放主线程
Task { @concurrent in
    try? data.write(to: fileURL, options: .atomic)
    signposter.endInterval("Writing To File", id)
}

坑:options: .atomic 会先写临时文件再重命名,虽然安全但会增加 I/O 开销。如果文件极大,即使放在后台也可能导致系统 I/O 拥塞,需要考虑分块写入。

最佳实践

  • 已有项目的迁移策略:先别急着改代码。用 Instruments 27 跑一遍 Time Profiler,盯住 CPU 使用率。如果卡顿时 CPU 飙高,用新的 Top Functions 视图找最耗时的函数去优化算法;如果卡顿时 CPU 闲置,切到 System Trace 查 I/O 和锁。
  • 新项目的采用建议:全面拥抱 OSSignpost。把网络请求、数据库查询、复杂 UI 渲染等关键路径全部用 beginIntervalendInterval 包起来。这会让你的 Instruments Trace 从一团乱麻变成清晰的业务时间线。
  • 实战中容易踩的坑:永远不要用 Debug 构建包去跑 Profiling。Debug 模式下的 Swift 编译器会关闭优化并插入大量运行时检查,导致测出来的性能数据毫无参考价值,甚至会把原本不卡的代码测出卡顿。

还有什么值得关注

  • Top Functions 模式:Call Tree 的救星,直接把最耗时的函数平铺展示,不用再痛苦地展开几十层调用栈找瓶颈。
  • Inspector 面板:右侧新增的上下文面板,选中 Timeline 里的任何 track 或事件,直接显示详细元数据和快捷操作。
  • 四步诊断心智模型:Apple 官方确立了 CPU saturation -> sampling data -> execution contention -> system blocking 的排查顺序,建议直接打印出来贴在工位上。
Instruments Swift Concurrency Time Profiler Performance OSSignpost