unLoop 的生命周期管理非常严谨。要理解它的退出,首先要明白它的运行核心——Mode Item。如果一个 Mode 里没有任何任务可做,RunLoop 就像失去了燃料的引擎,会自动熄火。
1. RunLoop 的自然退出条件
当通过 CFRunLoopRun() 或 [NSRunLoop run] 启动后,出现以下情况之一,RunLoop 会退出:
-
Mode 为空(最常见原因) :
当前运行的 Mode 中已经没有任何 Source(Source0 或 Source1)且没有任何 Timer。
注意:Observer 不算任务,只有 Observer 的 Mode 会立即退出。
-
超时 (Timeout) :
启动时设置了特定的过期时间(仅限于显式指定时间的启动方式)。
-
显式停止:
在代码中调用了停止函数(见下文)。
-
线程销毁:
RunLoop 依附于线程,线程销毁,RunLoop 自然终止。
2. 如何主动让 RunLoop 停止?
根据你启动 RunLoop 的方式,停止的方法也不同:
方案 A:使用底层的 CFRunLoopStop (推荐)
这是最直接、最有效的方法。
Objective-C
// 在当前 RunLoop 运行的线程中调用
CFRunLoopStop(CFRunLoopGetCurrent());
- 原理:它会给 RunLoop 发送一个停止信号。RunLoop 会在处理完当前这一次循环后安全退出。
- 适用性:适用于所有通过
CFRunLoopRun...系列函数启动的情况。
方案 B:移除所有 Mode Item (燃料耗尽法)
- 做法:将该 Mode 下所有的
NSTimer销毁,并移除所有的CFRunLoopSource。 - 缺点:非常繁琐,且不适合有系统自动添加 Source 的场景(如主线程)。
3. 陷阱:为什么 [NSRunLoop run] 停不掉?
这是开发者最常踩的坑。NSRunLoop 有三个启动方法:
-
run:永远停不掉。它实际上是在一个死循环里反复调用runMode:beforeDate:。即便你调用了CFRunLoopStop,它也只是退出了内层循环,外层死循环会立刻让它重新跑起来。 -
runUntilDate::在指定时间前不会停止,类似于上面的死循环。 -
runMode:beforeDate:(推荐) :-
优点:它只运行一次循环。
-
用法:通常配合一个
while循环和状态位使用:Objective-C
while (!self.shouldStop && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); -
停止方法:设置
self.shouldStop = YES并调用CFRunLoopStop。
-
4. RunLoop 停止的精准时机
当调用 CFRunLoopStop 后,RunLoop 并不是瞬间蒸发。它的退出动作发生在一次完整迭代的末尾。
- 流程:收到 Stop 指令 完成当前 Source/Timer 处理 通知 Observer (
kCFRunLoopExit) 彻底跳出do-while循环。
总结:停止 RunLoop 的最佳实践
| 启动方式 | 推荐停止方案 | 备注 |
|---|---|---|
CFRunLoopRun() | CFRunLoopStop() | 标准底层做法。 |
NSRunLoop run | 无法停止 | 除非销毁线程,否则不建议使用。 |
runMode:beforeDate: | 修改 While 循环条件 + CFRunLoopStop | 常驻子线程的最佳实践。 |
💡 进阶提示
在子线程使用常驻 RunLoop 时,务必在 while 循环里包裹一个 autoreleasepool。否则,由于 RunLoop 长期不退出,该线程产生的临时对象会一直堆积,直到 RunLoop 停止的那一刻才释放,容易引发内存峰值。