7-2.【OC】【RunLoop】RunLoop 的 Mode 有什么真实意义?为什么 NSTimer 在不同 Mode 下表现不同?

5 阅读3分钟

RunLoop 的 Mode(模式) 是 iOS 性能优化的核心设计。它的真实意义在于隔离:通过将不同类型的事件(Source/Timer/Observer)分发到不同的组里,确保在执行高优先级任务(如滑动列表)时,低优先级的任务不会干扰 UI 的流畅度。


1. Mode 的真实意义:资源调度与优先级隔离

一个 RunLoop 包含多个 Mode,每个 Mode 包含自己的事件源。RunLoop 每次启动只能运行在一种特定的 Mode 下。

  • 隔离干扰:当你滑动 UITableView 时,RunLoop 会从 kCFRunLoopDefaultMode 切换到 UITrackingRunLoopMode。此时,只有属于“追踪模式”的事件会被处理。
  • 节省性能:如果所有任务(网络回调、定时器、UI 刷新、手指追踪)都在一个池子里,滑动时的每一帧都可能被一个无关紧要的后台定时器阻塞,导致掉帧。

常见的五种 Mode

模式名称说明真实用途
Default默认模式应用空闲、处理简单逻辑时。
UITracking界面追踪模式用户触摸、滑动列表时强制切换到的模式。
Common占位模式它不是真正的 Mode,而是一个同步组(包含 Default 和 Tracking)。
Initialization初始化模式启动应用时进入,完成后不再使用。
Modal模态模式处理模态窗口(如弹窗)时的事件。

2. 为什么 NSTimer 在不同 Mode 下表现不同?

当你使用 scheduledTimerWithTimeInterval:... 创建定时器时,系统默认将其加入到 Default Mode

表现差异的根源

  1. 滑动前:RunLoop 运行在 Default 模式。定时器在注册表中,每隔一段时间被唤醒执行,一切正常。
  2. 滑动开始:一旦你手指触摸屏幕滚动列表,主线程的 RunLoop 会立即切换到 UITrackingRunLoopMode
  3. 定时器“失踪” :因为你的定时器只注册在 Default 模式下,而在 Tracking 模式的注册表中找不到它。RunLoop 此时会忽略所有 Default 模式下的 Source/Timer。
  4. 滑动停止:手指离开,RunLoop 切换回 Default,定时器发现自己“迟到”了,于是赶紧连续触发或继续运行。

3. 如何解决“定时器失效”?

方案 A:加入 Common Modes

将定时器手动添加到 NSRunLoopCommonModes 中。

Objective-C

NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  • 原理:Common Modes 相当于一个标签。系统会自动将标记为 Common 的 Timer 同步分发到所有被标记为 Common 的真 Mode 中(通常包括 Default 和 Tracking)。这样无论怎么滑动,RunLoop 都能找到它。

方案 B:使用 GCD Timer

GCD 的 dispatch_source_t 定时器不依赖 RunLoop。

  • 原理:它由内核直接调度,不受 Mode 切换影响,精度也更高。

总结

Mode 就像是 “不同的工作频道”

  • 当你专注看电影(滑动 UI)时,你的手机会自动屏蔽其他频道的通知(Default Timer),除非你把通知设为“特急”(Common Modes)。
  • 这种设计通过牺牲一部分后台任务的实时性,换取了 iOS 系统标志性的 “丝滑滑动体验”