7-6.【OC】【RunLoop】RunLoop observer 可以用来做哪些高级监控或优化?

4 阅读3分钟

CFRunLoopObserver 是深入 iOS 性能底层的“探针”。通过监听 RunLoop 的状态切换,可以实现许多在普通业务层无法完成的系统级优化。

以下是 Observer 的四大高级应用场景:


1. 实时卡顿检测 (Lag Monitor)

正如我们之前讨论的,这是 Observer 最经典的应用。

  • 原理:在子线程监控主线程的状态。如果主线程在 BeforeSourcesAfterWaiting 之间停留时间过长(通常设定为 16ms 或针对掉帧容忍度设为 200ms),则判定为卡顿。

  • 高级技巧

    • 堆栈聚合:当检测到卡顿,立即抓取所有线程堆栈,并根据函数符号进行聚合,直接定位出是哪个 C 函数或 OC 方法在拖慢速度。
    • 判定阈值动态化:在低端机和高端机上设置不同的阈值。

2. 界面流畅度 (FPS) 优化:空闲任务分发

如果你有大量的计算任务或耗时的 UI 设置(如设置圆角、复杂的文本计算),直接放在 cellForRowAtIndexPath 会导致滑动卡顿。

  • 原理:监听 kCFRunLoopBeforeWaiting(即将进入休眠)。

  • 策略

    1. 创建一个任务队列。
    2. 当 Observer 告知 RunLoop 即将空闲时,每次只从队列中取出一个小任务执行。
    3. 执行完一个后手动唤醒 RunLoop,让它检查是否有更高优先级的触摸事件。
  • 效果:将原本集中的计算压力“化整为零”,分散到多个 RunLoop 的间隙中,确保每一帧都能在 16ms 内完成。


3. AutoreleasePool 的内存精细化管理

App 运行时的自动释放池其实就是靠 Observer 维护的。

  • Entry (kCFRunLoopEntry) :创建一个 AutoreleasePool

  • BeforeWaiting (kCFRunLoopBeforeWaiting)

    1. 销毁旧的池子(触发内部所有对象的 release)。
    2. 立即创建一个新的池子。
  • Exit (kCFRunLoopExit) :销毁最后的池子。

高级应用:你可以模仿这一机制,在自己的后台线程中通过 Observer 确保长生命周期的线程不会因为 Autorelease 对象的堆积而导致内存激增。


4. 延迟执行与 UI 状态同步

有些任务我们希望在“所有 UI 修改都已生效”后执行,但又不希望放在下一个 dispatch_async

  • 场景:比如你想在视图已经 layout 完成并渲染在屏幕后,立即开始一个复杂的动画。
  • 做法:注册一个 BeforeWaiting 的 Observer,并在回调中执行任务。因为系统底层的渲染服务(Core Animation)也处于这个阶段,你可以通过控制 Observer 的 优先级 (Order) ,让自己在渲染逻辑之前或之后介入。

总结:Observer 监控点对照表

优化目标监听状态 (Activity)介入目的
卡顿/ANR 监控AfterWaiting / BeforeSources计算任务处理时间。
异步渲染分发BeforeWaiting在线程即将休息时处理低优先级任务。
内存池释放BeforeWaiting批量释放局部变量,降低内存水位。
启动性能分析Entry / Exit统计 App 启动后首个 Loop 的耗时。

⚠️ 注意事项

  • 优先级 (Order) :注册时有一个 order 参数。通常系统组件(如 CA 渲染)使用的是 0。如果你要做性能监控,建议 order 设置得尽量大(如 LONG_MAX),确保你是最后离开的;如果你要做预处理,则设置得小一些。
  • 开销:Observer 的回调非常频繁。不要在回调函数里写耗时逻辑,否则监控器本身就会变成卡顿源。