下面主要是对Run Loops官方文档的翻译及总结。
定义
run loop 是一个事件处理的循环,负责对工作进行调度,同时协调接收即将到来的任务。他的目的是确保线程在有任务的时候立即工作,没有任务的时候就歇着。
run loop 的管理不是完全自动的,你可以通过代码去运行线程的run loop,让他去处理特定的任务。包括主线程在内的所有线程,都不需要自己去创建run loop,他们本身内部就有runloop对象,只不过主线程中的runloop是在程序启动的时候就自动运行起来了,而主线程外的其他线程的runloop 需要用户自己去run。
剖析
run loop 就像它的名字一样,它是一个在线程内部的循环,用来运行那些负责处理即将进入线程的事件的程序。
run loop接收来自于两种类型数据源的事件;一种是Input source,负责传递异步事件,主要来自其他线程或者其他的应用程序;另外一种是Timer source 负责传递同步事件,主要来计划时间触发或者是重复间隔触发事件。当两种源类型的事件到达后,都用特定的程序来进行处理。
上图是run loop及其数据源的构想结构图,input source 将异步事件传递个对应的处理程序,最终调用runUntilDate:方法退出run loop,Timer将事件传递给对应的处理程序,但不会导致run loop 退出。
除了处理数据源的输入外,run loop还会为一些特性产生通知,只要注了run loop的观察者都可以接受到这些通知,并且在线程内做一些额外的工作。
-
Run Loop Modes
run loop mode 是被监控的input source和 timer source的集合,也是需被run loop通知的观察者的集合。每当你运行你的run loop 的时候,你都需要间接或直接的指定一个在内部运行的mode 。在run loop 运行的过程中,只监控与当前mode相关联的source,也只接受关联source传递的事件。而与其他mode相关联的source及传递的事件,只能等待到对应的mode才能被处理,因此你可以通过mode来过滤出不需要的数据源传递的事件。
系统默认提供了几种mode,你也可以自定义mode,当你自定义mode时,一定要在mode内添加一个或多个的 input source或者timer source 或者观察者以确保该mode有用。
-
系统预定义mode
-
1:NSDefaultRunLoopMode 默认mode,多数情况下run loop在这种mode下运行。
-
4:NSEventTrackingRunLoopMode 页面的滑动等运行在这用mode下
-
5: NSRunLoopCommonModes 这是一个可配置的通用mode组,将input source 和这个mode相关联,同时也会与组内的其他mode相关联,在Cocoa applications中,这个组中默认包括default, modal, event tracking modes,你也可以通过CFRunLoopAddCommonMode(::)方法将自定义的mode加入到这个组中。
-
-
input source
input source 以异步方式传递事件给线程。而基于input source 的事件源主要分为两类: Port-based input sources:监控你应用程序的Match端口;Custom input sources:监控自定义的事件源。他们两个对于系统来说没有任何差别,唯一的差别是是如何发出信号,Port-based是由系统的内核自动发出信号,Custom input 需要从其他线程手动的发送信号。
当你创建一个input source 的时候,你可将他分配给你的run loop 的一个或多个mode,mode会随时影响到哪个input source被监控,如果你的input source 没有在当前被监控的mode中,那么事件需要等到runloop 执行对应的mode时才会被执行(这就是如果你将轮播图的定时器加入在default mode中后,如果滑动页面时,轮播图不会动的原因,滑动页面时当前runloop运行的是track mode,解决办法是将定时器加入到common mode中,或者将定时器加入到新的线程,并且run该线程的runloop)。
-
Port-Based Sources、Custom Input Sources
详见
Configuring a Port-Based Input Source.
Defining a Custom Input Source
除上面外,系统定义了一个custom source 允许用户调用,Cocoa Perform Selector Sources,就是performSelectorOnMainThread:withObject:waitUntilDone:
等方法 -
Timer source
Timer source 是预先设置好的,在未来的某个时间向线程传递同步事件,是一种线程提醒自己做某事的方式。
Timer source 的产生虽然是基于时间的通知,但是他并不是实时的,和input source一样,它也需要和runloop的具体mode相关联。如果与之关联的mode没有被监控,那么timer将拥有不被触发。
详见Configuring Timer Sources
-
Run Loop Observers
和需要适当的同步或异步事件来触发的事件源不同,Run Loop Observers是由runloop自己在运行到对应的的位置的时去候触发的。用户可以由此根据不同的时机去做一些相应的处理。主要的可以关联的属性如下:
- 进入runloop
- runloop即将处理timer
- runloop即将处理input source
- runloop即将进入睡眠
- runloop被唤醒,在处理唤醒事件前
- 从runloop中退出
详细创建Configuring the Run Loop.
-
The Run Loop Sequence of Events
每次运行runloop它处理待处理事件的整个过程如下:
- 1.通知已经进入runloop
- 2.通知即将处理准备好的timer
- 3.通知即将处理所有非port based 的input source事件
- 4.处理那些待处理的非port based 的input source事件
- 5.如果有在等待的基于port based 的input source事件则立即去处理,然后到第9步骤
- 6.通知runloop即将进入睡眠
- 7.保持睡眠状态,直到发生任何一个如下事件:
- 有机遇port based 的input source事件到达
- fire一个timer
- 为runloop设置的超时值到期
- runloop被明确唤醒
- 8.通知线程被唤醒
-
- 处理待处理的事件
- 如果用户定义的timer被触发,处理timer事件,重启runloop,跳到第2步骤
- 如果input source 被触发,则传递事件
- 如果runloop是被明确唤醒的且还没有超时,则重新运行runloop,跳转到第2步骤
When Would You Use a Run Loop?
主线程的runloop是在整个应用开始运行的时候,由系统自动启动的,所以需要用户手动启动的只有其他的辅助线程,而在辅助线程中也要根据情况去考虑,是否有启动runloop的必要,runloop的预期是你需要和线程有更多的交互。
如果在下面的任意情况之一,那么就需要去启动runloop:
- 通过port或者custom input source 和其他线程进行交流
- 在线程内应用timer
- 应用任何的 performSelector..方法
- 保持线程以执行定期任务
Using Run Loop Objects
-
获得runloop对象
//获得当前线程的runloop对象
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
-
配置runloop
当你运行runloop前一定要先像其中加入至少一个input source 或者 timer,否则的话只要一运行,runloop就会停止。除了source 外,你也可以向其注册观察者,用 CFRunLoopObserverRef和`CFRunLoopAddObserver方法去创建与添加。
下面的代码展示了添加观察者的过程
void myfunc() {
NSLog(@"回调");
};
- (void)viewDidLoad {
[super viewDidLoad];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &myfunc, &context);
if (observer)
{
CFRunLoopRef cfLoop = [runloop getCFRunLoop];
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
[NSTimer scheduledTimerWithTimeInterval:1 target:self
selector:@selector(doFireTimer) userInfo:nil repeats:YES];
NSInteger loopCount = 10;
do
{
// Run the run loop 10 times to let the timer fire.
[runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
}
- (void)doFireTimer {
NSLog(@"timer fun");
}
为长期存在的线程配置runloop时,最好至少添加一个input source来接收消息。 虽然您可以附加一个定时器进入运行循环,但一旦定时器触发,它通常会失效,这会导致运行循环退出。 附加重复计时器可以使运行循环运行更长的时间,但是会涉及定期触发计时器以唤醒您的线程,这实际上是另一种形式的轮询。 相比之下,input source会等待事件到来的过程中,让线程保持睡眠状态。
Configuring Run Loop Sources
- Defining a Custom Input Source
- Configuring Timer Sources
- Configuring a Port-Based Input Source
略,详情请查看Configuring a Port-Based Input Source
大牛的文章从源码实现的角度介绍runloop,也可以去观摩相互印证,加深理解。
网友评论