使用 RunLoop
我们应该只在创建辅助线程的时候,才显示的运行一个 RunLoop.iOS app 会在应用启动的时候帮我 run 一个 runloop,而我们自己新建的辅助线程不会.
对于辅助线程,我们仍然需要判断是否需要启动一个 RunLoop.比如我们使用一个线程去处理一个预先定义的长时间的任务,我们应该避免启动 RunLoop.下面是官方document 提供的使用 RunLoop 的几个场景:
需要使用 Port-Based Input Source或者 Custom InputSource 和其他thread通讯时
需要在线程中使用 Timer
需要在线程中使用上文中提到的selector相关的方法
需要让线程周期性的执行某些工作
如果你选择使用 RunLoop, runloop 的设置和启动是比较直观的. 同时,你需要实现什么情况下从辅助线程中退出 runloop, 最好不要直接关闭线程,而是先退出 runloop.
如何创建和设置 runloop.代码:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW5
RunLoop 对系为增加 input source,timers,添加 observers 提供了主要的接口.每一个 thread 都有且仅有一个 runloop.Cocoa 中使用 NSRunLoop, CoreFoundation 中使用 CFRunLoopRef.
从线程中获取 RunLoop 对象
为了从当前 thread 中获取runloop 对象,具体步骤如下:
在 Cocoa中, 使用 [NSRunLoop currentRunLoop] ,就会返回当前线程的 runLoop 对象.
CoreFoundation 中使用 CFRunLoopRef.
CFRunLoopRef和 NSRunLoop 可以转化, NSRunLoop 使用getCFRunLoop方法就可以得到 CFRunLoopRef 对象
配置 RunLoop 对象
在辅助线程启动 runloop 之前,你必须至少在其中添加一个 input source 或者 timer.如果一个 runloop 中没有一个事件源sources, runloop 会在你启动它以后立即退出.
在添加了 source 以后,你可以给 runloop 添加 observers 来监测 runloop 的不同的执行的状态.为了加入 observer, 你应该创建一个 CFRunLoopObserverRef,使用 CFRunLoopAddObserver 函数添加 observer 到你的 runloop.
下面代码块显示了, 如何给 RunLoop 添加一个 observer .
- (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 中必须有一个 input source 或者 timer事件源.如果 runloop 启动时候,内部没有监听任何一个 input source 或者 timer, runloop 会立即 exit.
有以下几种启动 RunLoop 的方法:
1.没有设置特定的条件启动
2.设置一个时间限制
3.在一个特定的 mode
最简单的是一个无条件的启动 runloop,但是这也是最差的选择.如果没有设置任何条件,就会将 runloop 所在的 thread 进行永久的事件循环.你可以增加或者减少 input sources, timers,但是只有一种方法能够 kill 掉它.同时这种方式没办法让 runloop 在自定义的 mode 中运行.
替代无条件进入run loop更好的办法是用预设超时时间来运行runloop,这样runloop运作直到某一事件到达或者规定的时间已经到期。如果是事件到达,消息会被传递给相应的处理程序来处理,然后runloop退出。你可以重新启动runloop来等待下一事件。如果是规定时间到期了,你只需简单的重启runloop或使用此段时间来做任何的其他工作。
除了超时机制,你也可以使用特定的模式来运行你的runloop。模式和超时不是互斥的,他们可以在启动runloop的时候同时使用。模式限制了可以传递事件给run loop的输入源的类型,这在”Run Loop模式”部分介绍。
描述了线程的主要例程的架构。本示例的关键是说明了runloop的基本结构。本质上讲你添加自己的输入源或定时器到runloop里面,然后重复的调用一个程序来启动runloop。每次runloop返回的时候,你需要检查是否有使线程退出的条件成立。示例中使用了Core Foundation的run loop例程,以便可以检查返回结果从而确定run loop为何退出。若是在Cocoa程序,你也可以使用NSRunLoop 的方法运行run loop,无需检查返回值。
- (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);
}
可以递归的运行run loop。换句话说你可以使用CFRunLoopRun,CFRunLoopRunInMode或者任一NSRunLoop的方法在输入源或定时器的处理程序里面启动run loop。这样做的话,你可以使用任何模式启动嵌套的run loop,包括被外层run loop使用的模式。
退出 RunLoop
有两种方法可以让 runloop 退出:
- 给 runloop 设置超时事件
2.通知 runloop 停止
如果可以配置的话,推荐使用第一种方法。指定一个超时时间可以使run loop退出前完成所有正常操作,包括发送消息给run loop观察者。
使用CFRunLoopStop来显式的停止runloop和使用超时时间产生的结果相似。Runloop把所有剩余的通知发送出去再退出。与设置超时的不同的是你可以在无条件启动的run loop里面使用该技术。
尽管移除runloop的输入源和定时器也可能导致run loop退出,但这并不是可靠的退出run loop的方法。一些系统例程会添加输入源到runloop里面来处理所需事件。因为你的代码未必会考虑到这些输入源,这样可能导致你无法没从系统例程中移除它们,从而导致退出runloop。