7-7.【OC】【RunLoop】RunLoop 的退出条件是什么?如何主动让 RunLoop 停止?

3 阅读3分钟

unLoop 的生命周期管理非常严谨。要理解它的退出,首先要明白它的运行核心——Mode Item。如果一个 Mode 里没有任何任务可做,RunLoop 就像失去了燃料的引擎,会自动熄火。


1. RunLoop 的自然退出条件

当通过 CFRunLoopRun()[NSRunLoop run] 启动后,出现以下情况之一,RunLoop 会退出:

  1. Mode 为空(最常见原因)

    当前运行的 Mode 中已经没有任何 Source(Source0 或 Source1)且没有任何 Timer

    注意:Observer 不算任务,只有 Observer 的 Mode 会立即退出。

  2. 超时 (Timeout)

    启动时设置了特定的过期时间(仅限于显式指定时间的启动方式)。

  3. 显式停止

    在代码中调用了停止函数(见下文)。

  4. 线程销毁

    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 有三个启动方法:

  1. run永远停不掉。它实际上是在一个死循环里反复调用 runMode:beforeDate:。即便你调用了 CFRunLoopStop,它也只是退出了内层循环,外层死循环会立刻让它重新跑起来。

  2. runUntilDate::在指定时间前不会停止,类似于上面的死循环。

  3. runMode:beforeDate: (推荐)

    • 优点:它只运行一次循环。

    • 用法:通常配合一个 while 循环和状态位使用:

      Objective-C

      while (!self.shouldStop && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
      
    • 停止方法:设置 self.shouldStop = YES 并调用 CFRunLoopStop


4. RunLoop 停止的精准时机

当调用 CFRunLoopStop 后,RunLoop 并不是瞬间蒸发。它的退出动作发生在一次完整迭代的末尾

  • 流程:收到 Stop 指令 \rightarrow 完成当前 Source/Timer 处理 \rightarrow 通知 Observer (kCFRunLoopExit) \rightarrow 彻底跳出 do-while 循环。

总结:停止 RunLoop 的最佳实践

启动方式推荐停止方案备注
CFRunLoopRun()CFRunLoopStop()标准底层做法。
NSRunLoop run无法停止除非销毁线程,否则不建议使用。
runMode:beforeDate:修改 While 循环条件 + CFRunLoopStop常驻子线程的最佳实践

💡 进阶提示

在子线程使用常驻 RunLoop 时,务必在 while 循环里包裹一个 autoreleasepool。否则,由于 RunLoop 长期不退出,该线程产生的临时对象会一直堆积,直到 RunLoop 停止的那一刻才释放,容易引发内存峰值。