RunLoop用于管理事件的循环处理机制。运行循环在应用程序的主线程中自动启动,负责监听和分发各种事件,包括用户交互(如触摸事件)、定时器事件、选择器调用和其他异步回调。

运行循环的作用

运行循环的主要作用包括:

  • 处理输入事件:运行循环监听用户的输入,如触摸、点击和滑动事件,并将它们分发到适当的处理程序。
  • 调度定时器:运行循环管理定时器(NSTimer)的执行,确保在指定的时间触发事件。
  • 执行选择器调用:通过 performSelector:withObject:afterDelay: 等方法安排的选择器调用会在运行循环中执行。
  • 管理异步任务:运行循环与异步API协作,如网络请求,处理完成后的回调。
  • 保持线程活跃:运行循环使得主线程在没有工作时处于休眠状态,有工作时醒来处理,有效地管理CPU资源。

运行循环的组成

运行循环由以下几个核心组件组成:

  • 输入源(Input Sources):非基于端口的输入源(如用户交互事件)和基于端口的输入源(用于线程或进程间通信)。
  • 定时源(Timer Sources):定时器事件,可以在指定的时间点触发。
  • 运行循环模式(Run Loop Modes):运行循环可以配置为不同的模式,每种模式定义了运行循环在该模式下可以处理的输入源和定时器。这允许运行循环根据当前的活动调整其行为。

观察者

Observers(观察者)是一种监听RunLoop不同活动阶段的机制。是CFRunLoopObserver对象,可以被添加到RunLoop中,以便在RunLoop达到特定的运行阶段时接收通知。

CFRunLoopObserver可以关注以下几种RunLoop活动(事件):

  • kCFRunLoopEntry:进入RunLoop时触发。
  • kCFRunLoopBeforeTimers:RunLoop处理定时器之前触发。
  • kCFRunLoopBeforeSources:RunLoop处理输入源之前触发。
  • kCFRunLoopBeforeWaiting:RunLoop进入休眠等待输入源之前触发。
  • kCFRunLoopAfterWaiting:RunLoop被唤醒后,处理完唤醒事件之前触发。
  • kCFRunLoopExit:退出RunLoop时触发。

开发可以创建Observers并指定它们关注的活动,以及一个回调函数,当RunLoop达到这些活动时,回调函数将被调用。

创建和添加Observer的示例代码如下:

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    // 在RunLoop即将休眠前执行的代码
    NSLog(@"RunLoop is about to sleep.");
});

CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

在这个例子中创建了一个Observer,它会在RunLoop即将休眠前触发。使用CFRunLoopObserverCreateWithHandler函数来创建Observer,并提供了一个block作为回调函数。然后使用CFRunLoopAddObserver将Observer添加到当前的RunLoop中。

使用Observers可以更精细地控制代码的执行时机。然而,大多数情况下,不需要直接使用Observers,因为高级API(如NSTimerperformSelector:withObject:afterDelay:等)已经足够满足常见的需求。

RunLoop的运行逻辑

RunLoop的运行逻辑可以分解为以下步骤:

  1. 进入Loop:当RunLoop开始时,它会通知所有注册的Observers(观察者)一个即将进入循环的事件。

  2. 处理Timers:RunLoop会通知Observers它即将处理定时器(Timers)。

  3. 处理Sources:RunLoop通知Observers它即将处理输入源(Sources)。输入源可以分为两种:Source0和Source1。Source0只包含应用程序内部事件,如UI事件;Source1包含系统内核和其他线程的事件。

  4. 处理Blocks:如果有block被添加到RunLoop中,这些block会在这个阶段执行。

  5. 处理Source0:RunLoop处理Source0事件,这可能会导致更多的block被执行。

  6. 检查Source1:如果存在Source1事件,RunLoop会直接跳到第8步。

  7. 休眠:如果没有立即要处理的事件,RunLoop会通知Observers它即将休眠,并等待新的事件唤醒。

  8. 被唤醒:当RunLoop被事件唤醒时,它会通知Observers,并处理事件。这可能包括处理定时器、处理GCD异步派发到主队列的任务或处理Source1事件。

  9. 再次处理Blocks:处理完事件后,RunLoop会再次执行步骤4中的block。

  10. 决定如何操作:根据前面的执行结果,RunLoop会决定是继续循环还是退出。如果处理了事件并且还有更多的工作要做,它会回到步骤2;如果没有更多的工作或者接收到退出指令,它会准备退出。

  11. 退出Loop:在RunLoop退出前,它会通知所有Observers一个即将退出循环的事件。

RunLoop的其他注意事项

  • 主线程的RunLoop在应用程序启动时已经被自动创建和启动,因为主线程负责处理UI事件和其他用户交互。
  • 子线程的RunLoop默认不会启动,需要手动管理。如果在子线程中需要长时间运行的任务,并且需要处理事件,可能需要手动启动RunLoop。
  • RunLoop与线程是一一对应的,它在第一次获取时创建,在线程结束时销毁。