[OC RunLoop_翻译]一、介绍 & 二、剖析运行循环
[OC RunLoop_翻译]三、 什么时候使用运行循环 & 四、使用运行循环对象
[OC RunLoop_翻译]五、配置运行循环源
注:pdf翻译文档百度云下载链接,密码:zcs2
三、 什么时候使用运行循环?
唯一需要显式运行runloop
的时间是为应用程序创建辅助线程
时。应用程序主线程的运行循环是基础架构的重要组成部分。因此,应用程序框架提供运行主应用程序循环的代码,并自动启动该循环
。iOS中 UIApplication(或osx中的 NSApplication)的run
方法作为正常启动序列的一部分启动应用程序的主循环。如果您使用Xcode模板项目来创建应用程序,则永远不必显式调用这些例程。
对于辅助线程
,您需要确定是否需要运行循环
,如果需要,请自行配置并启动它
。您不需要在所有情况下都启动线程的运行循环
。例如,如果使用线程执行一些长时间运行的预定任务,则可以避免启动运行循环。运行循环用于需要与线程进行更多交互的情况。例如,如果您打算执行以下任一操作,则需要启动运行循环:
-
使用端口或自定义输入源与其他线程通信
. - 在线程上
使用定时器
。. - 在
Cocoa
应用程序中使用任何performSelector…
方法. -
保持线程执行周期性任务
.
如果确实选择使用运行循环,则配置和设置非常简单。与所有线程编程一样,您应该有一个计划,在适当的情况下退出辅助线程
。最好通过让线程退出干净地结束线程,而不是强制终止线程。使用runloop对象中介绍了有关如何配置和退出运行循环的信息。
四、使用运行循环对象
运行循环对象提供主接口
,用于向运行循环添加输入源、计时器和运行循环观察器
,然后运行
它。每个线程都有一个与之关联的运行循环对象
。在Cocoa中,此对象是 NSRunLoop类的实例。在低级应用程序中,它是指向 CFRunLoopRef不透明类型的指针
4-1、获取运行循环对象
要获取当前线程的运行循环,请使用以下方法之一:
- 在
Cocoa
应用程序中,使用 NSRunLoop的 currentRunLoop类方法检索NSRunLoop
对象。. - 使用 CFRunLoopGetCurrent函数.
尽管它们不是免费的桥接类型,但是您可以在需要时从NSRunLoop
对象获取CFRunLoopRef
不透明类型。 NSRunLoop
类定义了一个 getCFRunLoop 方法,该方法返回可以传递给Core Foundation例程的CFRunLoopRef
类型。由于两个对象都引用同一个运行循环,因此您可以根据需要混合对NSRunLoop
对象和CFRunLoopRef
不透明类型的调用。
4-2、配置运行循环
在辅助线程上运行runloop之前,必须向其添加至少一个输入源或计时器
。如果运行循环没有任何要监视的源,则当您尝试运行它时,它将立即退出。有关如何将源添加到运行循环的示例,请参见 配置运行循环源。
除了安装源代码之外,还可以安装运行循环观察器
,并使用它们来检测运行循环的不同执行阶段
。要安装运行循环观察器,需要创建一个 CFRunLoopObserverRef不透明类型,并使用 CFRunLoopAddObserver函数将其添加到运行循环中。运行循环观察者必须使用Core Foundation创建,即使对于Cocoa应用程序也是如此
。
清单3-1显示了一个线程的主例程,该线程将一个运行循环观察器附加到它的运行循环
上。该示例的目的是向您展示如何创建运行循环观察器
,因此代码只需设置一个运行循环观察器来监视所有运行循环活动
。基本处理程序例程(未显示)只是在处理计时器请求时记录运行循环活动。
Listing 3-1 创建运行循环观察者
- (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.运行运行循环10次,让计时器启动。
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
}
在为长生存期线程配置运行循环
时,最好至少添加一个输入源来接收消息
。虽然您可以在只附加计时器的情况下进入运行循环,但一旦计时器触发,它通常会失效,这将导致运行循环退出。附加一个重复计时器可以使运行循环保持较长时间运行,但是会涉及定期触发计时器以唤醒线程,这实际上是轮询的另一种形式。相比之下,输入源等待事件发生,让线程一直处于休眠状态。
4-3、启动运行循环
只有应用程序中的辅助线程才需要启动运行循环
。运行循环必须至少有一个要监视的输入源或计时器
。如果未连接,则运行循环将立即退出。
有几种启动运行循环的方法
,包括以下几种:
无条件
设定时间限制
在特定模式下
无条件
地进入运行循环是最简单
的选择,但也是最不可取的
。无条件地运行运行循环会将线程放入一个永久循环
中,这使您几乎无法控制运行循环本身。您可以添加和删除输入源和计时器,但停止运行循环的唯一方法是终止它。也无法在自定义模式下运行runloop
。
与其无条件地运行runloop,不如使用超时值运行runloop
。当您使用超时值时,运行循环将运行直到事件到达或指定的时间到期为止
。如果事件到达,则将该事件分派到处理程序进行处理,然后退出运行循环
。然后,您的代码可以重新启动运行循环以处理下一个事件。如果分配的时间到期了,您可以简单地重新启动运行循环或使用该时间进行任何必要的内务处理。
除了超时值之外,您还可以使用特定模式运行runloop
。模式和超时值不是互斥的,并且在启动运行循环时都可以使用。模式限制了将事件传递到运行循环的源的类型, Run Loop Modes中对此进行了详细描述。
清单3-2显示了一个线程主入口例程的框架版本。本质上,您将输入源和计时器添加到运行循环中,然后反复调用其中一个例程来启动运行循环
。每次运行循环例程返回时,您都要检查是否出现了可能需要退出线程的条件。该示例使用Core Foundation运行循环例程,以便可以检查返回结果并确定为什么退出运行循环。如果您使用的是Cocoa,并且不需要检查返回值,则也可以使用NSRunLoop类的方法以类似的方式运行运行循环。 (有关调用NSRunLoop类的方法的运行循环的示例,请参见清单3-14。)
Listing 3-2 运行runloop
- (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.
//检查此处是否有其他退出条件,并根据需要设置done变量。
}
while (!done);
// Clean up code here. Be sure to release any allocated autorelease pools.
}
可以递归地运行runloop。换句话说,可以调用 CFRunLoopRun, CFRunLoopRunInMode,或任何NSRunLoop
方法,以便从输入源或计时器的处理程序例程中启动运行循环。这样做时,您可以使用任何要运行嵌套运行循环的模式,包括外部运行循环使用的模式
4-4、退出运行循环
在处理事件之前,有两种方法可以使运行循环退出
:
-
配置运行循环以使用超时值运行
. -
告诉runloop停止
.
如果可以管理的话,使用超时值当然是首选
。指定一个超时值可以让运行循环在退出之前完成其所有的正常处理,包括向运行循环观察者发送通知。
使用 CFRunLoopStop函数显式停止运行循环会产生类似于超时的结果
。运行循环将发出所有剩余的运行循环通知,然后退出。区别
在于您可以在无条件启动的运行循环中使用
此技术
尽管删除运行循环的输入源和计时器也可能导致运行循环退出,但这不是停止运行循环的可靠方法。一些系统例程将输入源添加到运行循环中以处理所需的事件。因为您的代码可能不知道这些输入源,所以它将无法删除它们,这将阻止运行循环退出
4-5、线程安全和运行循环对象
线程安全性取决于您使用哪个API来操纵运行循环
。 Core Foundation
中的函数通常是线程安全
的,可以从任何线程中调用
。但是,如果执行的操作更改了运行循环的配置,那么只要可能,最好从拥有运行循环的线程进行更改
Cocoa NSRunLoop类本质上不像其Core Foundation对应类那样具有线程安全性。如果使用NSRunLoop类来修改运行循环,则应仅从拥有该运行循环的同一线程进行修改
。将输入源或计时器添加到属于不同线程的运行循环中可能会导致代码崩溃或行为异常。
网友评论