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(如 NSTimer
、 performSelector:withObject:afterDelay:
等)已经足够满足常见的需求。
RunLoop的运行逻辑
RunLoop的运行逻辑可以分解为以下步骤:
-
进入Loop :当RunLoop开始时,它会通知所有注册的Observers(观察者)一个即将进入循环的事件。
-
处理Timers :RunLoop会通知Observers它即将处理定时器(Timers)。
-
处理Sources :RunLoop通知Observers它即将处理输入源(Sources)。输入源可以分为两种:Source0和Source1。Source0只包含应用程序内部事件,如UI事件;Source1包含系统内核和其他线程的事件。
-
处理Blocks :如果有block被添加到RunLoop中,这些block会在这个阶段执行。
-
处理Source0 :RunLoop处理Source0事件,这可能会导致更多的block被执行。
-
检查Source1 :如果存在Source1事件,RunLoop会直接跳到第8步。
-
休眠 :如果没有立即要处理的事件,RunLoop会通知Observers它即将休眠,并等待新的事件唤醒。
-
被唤醒 :当RunLoop被事件唤醒时,它会通知Observers,并处理事件。这可能包括处理定时器、处理GCD异步派发到主队列的任务或处理Source1事件。
-
再次处理Blocks :处理完事件后,RunLoop会再次执行步骤4中的block。
-
决定如何操作 :根据前面的执行结果,RunLoop会决定是继续循环还是退出。如果处理了事件并且还有更多的工作要做,它会回到步骤2;如果没有更多的工作或者接收到退出指令,它会准备退出。
-
退出Loop :在RunLoop退出前,它会通知所有Observers一个即将退出循环的事件。
RunLoop的其他注意事项
- 主线程的RunLoop在应用程序启动时已经被自动创建和启动,因为主线程负责处理UI事件和其他用户交互。
- 子线程的RunLoop默认不会启动,需要手动管理。如果在子线程中需要长时间运行的任务,并且需要处理事件,可能需要手动启动RunLoop。
- RunLoop与线程是一一对应的,它在第一次获取时创建,在线程结束时销毁。