基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

RunLoop 内部逻辑(六步流程)

知识点图片

RunLoop 的核心逻辑主要是在 CoreFoundation 框架下的 CFRunLoop.c 文件中的 __CFRunLoopRun 函数里实现的。

虽然网上常说“六步”,但严格对应源码来看,其内部逻辑更为细致,通常分为 10 个关键节点。为了方便理解,我们可以将其归纳为几个大的阶段。

以下是 RunLoop 内部逻辑的详细流程解析:

核心流程图解

RunLoop 的本质是一个 do...while 循环,其核心在于“接收消息 -> 等待 -> 处理”

第一阶段:准备与通知 (Entry & Pre-check)

  1. 通知 Observers:即将进入 Loop

    • 状态:kCFRunLoopEntry
    • 说明:告诉观察者,RunLoop 启动了。
  2. 通知 Observers:即将处理 Timers

    • 状态:kCFRunLoopBeforeTimers
    • 说明:通知观察者即将处理定时器回调。
  3. 通知 Observers:即将处理 Sources

    • 状态:kCFRunLoopBeforeSources
    • 说明:通知观察者即将处理输入源(非基于 Port 的 Source0)。
  4. 处理 Source0 (非基于 Port 的源)

    • 操作:执行 performSelector:onThread: 等由应用层触发的事件。
    • 关键点:如果处理完 Source0 后,发现有 Source1 (基于 Port 的源) 已经准备好了,或者有主队列的 GCD 任务,可能会直接跳过休眠,去处理消息。
  5. 检查 Source1 (基于 Port 的源)

    • 操作:如果有 Source1 处于 Ready 状态,直接跳转到 第9步 (处理消息)。
    • 注:这是为了性能优化,如果有事做就不必休眠。

第二阶段:休眠 (Sleep)

  1. 通知 Observers:即将进入休眠

    • 状态:kCFRunLoopBeforeWaiting
    • 说明:这是 RunLoop 即将挂起、释放 CPU 资源的关键时刻。
  2. 休眠 (Wait for Mach Port)

    • 操作:调用 mach_msg_trap,从用户态切换到内核态
    • 说明:线程停止运行,等待被唤醒。
    • 唤醒条件(满足其一即可):
      • 基于 Port 的 Source1 事件到达(如屏幕点击、网络数据)。
      • Timer 定时器时间到了。
      • GCD 主队列(Main Dispatch Queue)有任务分发。
      • RunLoop 设置的超时时间到了。

第三阶段:唤醒与处理 (Wake Up & Handle)

  1. 通知 Observers:刚从休眠中唤醒

    • 状态:kCFRunLoopAfterWaiting
    • 说明:线程被唤醒,重新开始工作。
  2. 处理唤醒事件 (核心处理逻辑)

    • 根据被唤醒的原因,执行相应的任务:
      • 如果是 Timer 唤醒:触发 Timer 的回调。
      • 如果是 GCD 唤醒:执行 dispatch_async(dispatch_get_main_queue(), block) 中的 block。
      • 如果是 Source1 唤醒:处理系统内核发来的事件(如触摸、系统通知)。

第四阶段:退出或重试 (Exit or Loop)

  1. 决定下一步
    • 检查是否需要退出 Loop:
      • 如果是 kCFRunLoopRunFinished
      • 如果指定了超时时间且已超时。
      • 如果外部强制停止。
    • 如果未退出:跳转回 第2步,开始新的一轮循环。
    • 如果退出:通知 Observers kCFRunLoopExit

总结版(六步记忆法)

如果你为了面试或快速记忆,可以将上述繁琐步骤浓缩为以下“六步”逻辑:

  1. Notify Entry:通知进入 RunLoop。
  2. Process Pre-Sleep:通知处理 Timer/Source0,并执行 Source0。如果有 Source1 待处理,跳过休眠。
  3. Notify Sleep:通知即将休眠。
  4. Sleep:调用 mach_msg 等待唤醒(Trap into Kernel)。
  5. Notify Wake:通知被唤醒。
  6. Handle Event:处理唤醒源(Timer / GCD / Source1),然后决定是 Loop 还是 Exit。

关键概念补充

  • Source0:非基于 Port 的,用于应用层事件(如 performSelector)。需要手动标记为待处理并唤醒 RunLoop。
  • Source1:基于 Mach Port 的,用于系统内核事件(如触摸、硬件中断)。能主动唤醒 RunLoop。
  • GCD 与 RunLoop:只有 Main Dispatch Queue 的任务是由 RunLoop 处理的,其他队列由 GCD 线程池直接处理。
  • 用户态/内核态切换:RunLoop 的核心价值在于 Step 7,利用 mach_msg 让线程在没有消息时真正“停下来”不占用 CPU,有消息时又能毫秒级响应。
00:00
00:00