RunLoop
RunLoop 就是一个事件处理的循环,用来不停的调动工作和处理输入事件。
使用 RunLoop 的目的就是为了节省 CPU 效率,线程在有工作的时候忙于工作,而没工作的时候处于休眠状态。
一、RunLoop剖析
Structure of a Run Loop and its sources
上图显示了线程的输入源
A、基于端口的输入源(Port Sources)
B、自定义输入源(Custom Sources)
C、Cocoa执行Selector的源("performSelector...方法" Sources)
D、定时源(Timer Sources)
线程针对上面不同的输入源,有不同的处理机制
A、handlePort: ---处理基于端口的输入源
B、customSrc: ---处理用户自定义输入源
C、mySelector: ---处理Selector的源
D、timerFired: ---处理定时源
注: 线程除了处理输入源,RunLoops也会生成关于 RunLoop行为的通知(Notifacation)。
Run Loop观察者(Run-Loop Observers)可以收到这些通知,并在线程上面使用它们来做额外的处理
===在新线程的Run Loop中注册Observers:
---编写一个带有观测者的线程加载程序
- (void)observerRunLoop
{
// 建立自动释放池
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 获得当前thread的Run loop
NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
// 设置Run Loop observer的运行环境
CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};
// 创建Run loop observer对象
// 第一个参数用于分配该observer对象的内存
// 第二个参数用以设置该observer所要关注的的事件,详见回调函数myRunLoopObserver中注释
// 第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行
// 第四个参数用于设置该observer的优先级
// 第五个参数用于设置该observer的回调函数
// 第六个参数用于设置该observer的运行环境
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
if(observer)
{
// 将Cocoa的NSRunLoop类型转换程Core Foundation的CFRunLoopRef类型
CFRunLoopRef ç = [myRunLoop getCFRunLoop];
// 将新建的observer加入到当前的thread的run loop
CFRunLoopAddObserver(cfRunLoop, observer, kCFRunLoopDefaultMode);
}
// Creates and returns a new NSTimer object and schedules it on the current run loop in the default mode
[NSTimer scheduledTimerWithTImeInterval:0.1 target:self selector:@selector(doFireTimer:) userInfor:nil repeats:YES];
NSInteger = loopCount = 10;
do
{
// 启动当前thread的run loop直到所指定的时间到达,在run loop运行时,run loop会处理所有来自与该run loop联系的input sources的数据
// 对于本例与当前run loop联系的input source只有Timer类型的source
// 该Timer每隔0.1秒发送触发时间给run loop,run loop检测到该事件时会调用相应的处理方法(doFireTimer:)
// 由于在run loop添加了observer,且设置observer对所有的run loop行为感兴趣
// 当调用runUntilDate方法时,observer检测到run loop启动并进入循环,observer会调用其回调函数,第二个参数所传递的行为时kCFRunLoopEntry
// observer检测到run loop的其他行为并调用回调函数的操作与上面的描述相类似
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSiceNow:1.0]];
// 当run loop的运行时间到达时,会退出当前的run loop,observer同样会检测到run loop的退出行为,并调用其回调函数,第二个参数传递的行为是kCFRunLoopExit.
--loopCount;
}while(loopCount);
// 释放自动释放池
[pool release];
}
===observer的回调函数:
void myRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
switch(activity)
{
// The entrance of run loop, before entering the event processing loop.
// This activity occurs once for each call to CFRunLoopRun / CFRunLoopRunInMode
case kCFRunLoopEntry:
NSLog(@"run loop entry");
break;
// Inside the event processing loop before any timers are processed
case kCFRunLoopBeforeTimers:
NSLog(@"run loop before timers");
break;
// Inside the event processing loop before any sources are processed
case kCFRunLoopBeforeSources:
NSLog(@"run loop before sources");
break;
// Inside the event processing loop before the run loop sleeps, waiting for a source or timer to fire
// This activity does not occur if CFRunLoopRunInMode is called with a timeout of o seconds
// It also does not occur in a particular iteration of the event processing loop if a version 0 source fires
case kCFRunLoopBeforeWaiting:
NSLog(@"run loop before waiting");
break;
// Inside the event processing loop after the run loop wakes up, but before processing the event that woke it up
// This activity occurs only if the run loop did in fact go to sleep during the current loop
case kCFRunLoopAfterWaiting:
NSLog(@"run loop after waiting");
break;
// The exit of the run loop, after exiting the event processing loop
// This activity occurs once for each call to CFRunLoopRun and CFRunLoopRunInMode
case kCFRunLoopExit:
NSLog(@"run loop exit");
break;
/*
A combination of all the preceding stages
case kCFRunLoopAllActivities:
break;
*/
default:
break;
}
}
1,Run Loop模式---是所有要监测的输入源和定时源以及要通知的run loop注册观察者的集合。在run loop运行过程中,只有和模式相关的源才会被监测并允许他们传递事件消息。相反,没有被添加的输入源将会被过滤。
可以自定自己的Run Loop模式,但是每个模式必须有一个或者多个输入源,定时源或者run loop的观察者,否则,run loop直接退出,Run Loop模式将没有意义。
另,模式区分基于事件的源而非事件的种类。例如,不能使用模式只选择处理鼠标按下或者键盘事件。可以使用模式监听端口,而暂停定时器或者改变其他源或者当前模式下处于监听状态run loop观测着。
表1-1列出了cocoa和Core Foundation预先定义的模式。
2,Run Loop的输入源
A,基于端口的输入源
通过内置的端口相关的对象和函数,创建配置基于端口的输入源。相关的端口函数---CFMachPort/CFMessagePortRef/CFSocketRf
B,自定义输入源
自定义输入源使用CFRunLoopSourceRef对象创建,它需要自定义自己的行为和消息传递机制
C,Cocoa执行Selector的源
和基于端口的源一样,执行Selector的请求会在目标线程上序列化,减缓在线程上允许许多各方法容易引起的同步问题。两者区别:一个Selector执行完成后会自动从Run Loop上移除。
Table:Performing selectors on other threads
D,定时源
在预设的时间点同步方式传递消息,定时器是线程通知自己做某事的一种方法。
E,Run Loop观察者
源是合适的同步/异步事件发生时触发。观察者则是在Run Loop本身运行的特定时候触发。观察者触发的相关事件(参考上面红色程序里面的函数:myRunLoopObserves(...))
1)Run Loop入口
2)Run Loop何时处理一个定时器
3)Run Loop何时处理一个输入源
4)Run Loop何时进入休眠状态
5)Run Loop何时被唤醒,但在唤醒之前要处理的事件
6)Run Loop终止
注: 1,观察者通过CFRunLoopObserverRef对象创建的
2,观察者会在相应事件发生之前传递消息,所以通知的时间和事件实际发生的时间之间肯定有误差
F,Run Loop 的事件队列--包括观察者的事件队列
1)通知观察者Run Loop已经启动
2)通知观察者任何即将要开始的定时器
3)通知观察者任何即将启动的非基于端口的输入源
4)启动任何准备好的非基于端口的源
5)如果基于端口的源准备好了并处于等待状态,立即启动;并进入步骤9
6)通知观察者线程进入休眠
7)将线程置于休眠直到下面任一事件发生
A)某一事件到达基于端口的源
B)定时器启动
C)Run Loop设置的时间已经超时
D)Run Loop被显式唤醒
8)通知观察者线程被唤醒
9)处理未处理的事件
A)如果用户定义的定时器启动,处理定时器事件并重启Run Loop,进入步骤2
B)如果输入源启动,传递相应消息
C)如果Run Loop被显式唤醒而且时间还没有超时,重启Run Loop,进入步骤2
10)通知观察者Run Loop结束