从底层的 CFRunLoop.c 源码结构来看,RunLoop 的运行完全依赖于它的三个核心组件。通常我们称之为 Source、Timer 和 Observer。
这三个组件被统称为 Mode Item,因为它们必须被加入到某个特定的 Mode 中才能生效。
1. Source (事件源)
Source 是 RunLoop 的“体力劳动者”,负责接收并处理各种外部事件。它又细分为两类:
-
Source0 (非系统内核管理) :
- 职责:处理 App 内部事件。它并不能主动唤醒 RunLoop。
- 特点:你需要先调用
CFRunLoopSourceSignal(source)将其标记为待处理,然后手动调用CFRunLoopWakeUp唤醒 RunLoop,它才会执行。 - 常见例子:触摸事件处理、
performSelector:onThread:。
-
Source1 (基于 Mach Port / 系统内核管理) :
- 职责:处理来自系统内核或其他进程的消息。
- 特点:具备主动唤醒 RunLoop 的能力。当 Mach Port 收到消息时,内核会自动唤醒处于休眠状态的线程。
- 常见例子:硬件交互(如点击屏幕后的底层电信号转分发)、进程间通信(IPC)。
2. Timer (定时器)
Timer 是 RunLoop 的“闹钟”,负责在预设的时间点唤醒线程执行任务。
-
职责:基于时间的特定回调。它实际上也是一种特殊的 Source(在底层被封装为
CFRunLoopTimerRef)。 -
特点:
- 受 Mode 影响:正如我们之前聊过的,如果 Timer 所在的 Mode 不是当前运行的 Mode,定时器就不会触发。
- 非高精度:如果 RunLoop 正在执行一个耗时的 Source0 任务,Timer 的回调会被推迟(Tolerance)。
-
常见例子:
NSTimer、performSelector:withObject:afterDelay:。
3. Observer (观察者)
Observer 是 RunLoop 的“监视器”,它并不处理具体的业务逻辑,而是负责监控 RunLoop 本身的运行状态。
-
职责:在 RunLoop 运行的各个关键节点(时间点)触发回调,让我们可以介入做一些额外工作。
-
特点:可以监听以下状态:
kCFRunLoopEntry:即将进入 RunLoop。kCFRunLoopBeforeTimers:即将处理 Timer。kCFRunLoopBeforeSources:即将处理 Source。kCFRunLoopBeforeWaiting:即将进入休眠(这是处理界面刷新的黄金时机)。kCFRunLoopAfterWaiting:刚从休眠中唤醒。kCFRunLoopExit:即将退出。
-
常见例子:AutoreleasePool 的创建与销毁、UI 界面的布局刷新(Layout Subviews)、卡顿监控。
总结:三者的协同工作
我们可以把 RunLoop 比作一个待命的士兵:
- Source 是他的长官,下达具体的战斗指令(点击、网络请求)。
- Timer 是他的闹钟,提醒他什么时候该巡逻(定时任务)。
- Observer 是他的随从,记录他什么时候睡觉、什么时候起床,并在他睡觉前提醒他把地扫了(释放内存池、刷新 UI)。
| 组件 | 核心职能 | 是否能唤醒 RunLoop |
|---|---|---|
| Source0 | 内部事件(如点击处理) | 否 |
| Source1 | 内核/系统消息 | 是 |
| Timer | 延迟/定时任务 | 是 |
| Observer | 状态监控 | 否 |