RunLoops
概念
runloop是线程相关的基础对象,runloop是一个被用来处理事件或者timer的循环,runloop的目的是用来让线程在需要的时候运行,并且在不需要的时候休眠。
runloop的控制并不是自动的,你必须要手动管理。cocoa 和 foundation 提供了runloop对象来帮助你管理runloop,因而你不用创建。比如主线程就有自己的runloop,并且是自动运行的。子线程的runloop需要手动运行。需要注意的是,framwork的线程会自动建立并且作加入到主线程中作为应用的启动。
runloop分解
runloop就像他的名字一样是一个不断循环运行的环,runloop可以接受两种数据来源,
1. input source,可能是来源于其他线程的数据 会导致执行runUntilDate 执行
2. timer 不会导致执行runUntilDate 执行
除了可以处理输入的source,通过注册观察者也可以拿到runloop的通知代理。
runloop mode
mode可以很好的把事件和timer进行分类,这样可以更好地处理和管理不同类型的数据源,
cocoa 和 coreFoundation 可以创建一些默认的或者一些其他常用的mode。当然也可以自定义一个mode,但是所有的mode的runloop都是一样的只是名字不同。
同一时间只能进入一个mode,所以可以优先处理高优先级的mode,等待优先级高的mode结束之后再处理其他mode
几种不同的mode
mode | name | description |
---|---|---|
default | kCFRunLoopDefaultMode | 最常用 |
Modal | NSModalPanelRunLoopMode | 可用于modal panels |
EventTracking | NSEventTrackingRunLoopMode | 可用于检测滑动或者其他用户操作的操作环 |
CommonModes | kCFRunLoopCommonModes | mode集合,可以自己配置,默认有default mode,可添加。 |
input sources
port-based sources
cocoa和corefoundation 中corefondation需要创建port和source 需要使用port对象进行管理。
custom input source
使用 CFRunLoopSourceRef 创建相关的对象和回调方法,那么cf就会自动在指定时间调用这些回调,从而可以配置source
事件到达时需要自定义转发机制,这部分需要提供数据源和转发机制。
Cocoa Perform Selector Sources
使用一些api完成进程通讯,可以减轻很懂工作,并且可以自动移除source
在子线程中必须自己启动runloop,或者你甚至可以在程序一启动就立刻开启一个runloop,这样的话,你就可以一直在这个线程去做数据处理了。
TImer source
timer 的运行必须在runloop相同的mode下,并且需要等待runloop结束当前的handler处理才会执行。
timer的执行并不会按照fire的时间,而是以schedule 的时间为准。
NSMachPort
创建np(NSMachPort)并且添加到runloop中,添加子线程时将np传给子线程,子线程也可以用这个np去传递消息回来。
首先NSMarchPort是一个task内线程之间异步通信用的
你需要在你的两个object内重写handlePortMessage:方法来截取接收到的消息,使用NSPortMessage来发送消息
消息一般都是有唯一id和数据组成的
数据需要经过序列化通过NSPortMessage的components来传递
接收到数据之后需要进行反序列化
runloop 的观察者
可以添加一些runloop的观察者,这样就可以在指定时间观察到runloop的执行步骤,并且在这些步骤中做一些准备工作。你可以检测到的runloop执行
1. 开始
2. timer执行
3. inputsource 执行
4. 休眠前
5. 唤醒前。
6. 退出。
可以通过CFRunLoopObserverRef,使用例程
- (void)threadMain
{
// The application uses garbage collection, so no autorelease pool is needed.
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create a run loop observer and attach it to the run loop.
CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
if (observer)
{
CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
// Create and schedule the timer.
[NSTimer scheduledTimerWithTimeInterval:0.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.
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
}
runloop的执行过程
runloop的观察者虽然可以检测到runloop的执行步骤,但是由于是异步检查,所以时间可能不会非常精准,可以使用awake-from-sleep notification
去校准时间。
我们可以使用runloop对象直接唤醒,但是也可以直接给他一个inputsource,runtime也会被唤醒执行任务。
什么时候会使用一个runloop呢?
程序的主线程无需启动,因为会自动创建,runloop也会自动创建。
对于子线程,如果你需要更加自由的配置runloop,那么你可能就需要自己管理了。比如如下的场景你可能就需要使用runloop了
- 线程间的通讯,或者自定义输入源
- timers
- perforselector
- 保存一个线程一直运行。
必须恰当的关闭一个线程,从而避免内存泄露
如何使用一个runloop
runloop是一个用来管理线程的对象,可以给线程添加事件来源,timer来源,runloop观察者,一个线程都有一个唯一的runloop,可以用 currentRunLoop
获取。
配置runloop
在runloop开始前,必须给其添加事件源,不然runloop就会直接休眠了。
初次之外,也可以添加观察者到runloop,这样就可以监测runloop的执行,如上一个代码片。
当需要保持一个长执行的runloop的时候,最好添加一个inputsorce来接受和发送信息。虽然一个repeqt timer也可以做到,但是不如inputsource效率高。
开启runloop
可以用以下三种方式开启runloop
无条件开启
无条件开启会导致runloop一直运行,而且无法控制设置一个时间期限
在deadline到来之前或者Event到来之前,runloop会一直执行。当有事件的时候,runloop就会把事件分发给处理着,然后退出。然后就可以执行下一个任务了。以某个mode开启。
除了使用timeout也可以使用mode开启runloop
使用的大概方式
例子使用了CFRunloopMode
- (void)skeletonThreadMain
{
// Set up an autorelease pool here if not using garbage collection.
BOOL done = NO;
// Add your sources or timers to the run loop and do any other setup.
do
{
// Start the run loop but return after each source is handled.
SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
// If a source explicitly stopped the run loop, or if there are no
// sources or timers, go ahead and exit.
if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
done = YES;
// Check for any other exit conditions here and set the
// done variable as needed.
}
while (!done);
// Clean up code here. Be sure to release any allocated autorelease pools.
}
结束runloop
有两种方法可以结束runloop
- 设定超时
- 手动结束
设定超时是比较好的一种方法,可以让runloop尽量回收他的资源,并且完成包括notification等的执行过程。
使用CFRunloopStop会让runloop像timeout一样结束,可以让他在结束之前完成notification等动作再结束。
移除输入源虽然可以让runloop停止,但是这个方法并不可靠,因为有可能有一些inputsource你并不知道。
线程安全和runloop对象
Core Foundation 中的api一般都是线程安全的,并且可以在任何runloop中使用,包括你不控制的runloop。
NSRunloop 中的API最好只用在你管理的runloop里面。