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。
表现差异的根源
- 滑动前:RunLoop 运行在 Default 模式。定时器在注册表中,每隔一段时间被唤醒执行,一切正常。
- 滑动开始:一旦你手指触摸屏幕滚动列表,主线程的 RunLoop 会立即切换到 UITrackingRunLoopMode。
- 定时器“失踪” :因为你的定时器只注册在 Default 模式下,而在 Tracking 模式的注册表中找不到它。RunLoop 此时会忽略所有 Default 模式下的 Source/Timer。
- 滑动停止:手指离开,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 系统标志性的 “丝滑滑动体验” 。