什么是Run Loops
RunLoops是与线程相关联的基础部分,一个Run Loop就是事件处理循环,他是用来调度和协调接收到的事件处理。使用RunLoop的目的,就是使的线程有工作需要做的时候忙碌起来,当没事做的时候,又可以使得线程休眠。
RunLoop管理不是自动的。我们必须手动设计线程代码,在合适的时候启动RunLoop,并回应到相应的事件。Cocoa和Core Foundation都提供了run loop对象来帮助我们配置和管理线程的run loop。我们的应用没有必要显式地创建这些对象;每个线程,包括应用程序的主线程,都有一个与之关联的run loop。只有子线程才需要显式地运行其run loop。App会将自动配置和来运行主线程的run loop的任务作为应用程序启动处理的一部分。
一个RunLoop的结构
Run Loop就像它的名字一样,它使得线程进入事件循环,能对到来的事件启动事件处理。你的代码中提供了流程控制说一句来实现run loop实实在在的循环部分,换句话说,你的代码提供了while或者for循环来驱动run loop。在你的循环中,你使用run loop对象在事件到达时,运行事件处理的代码并调起已安装的处理程序。
Run Loop接收来自两种不同类型的源(sources)的事件:
输入源:异步传递事件,通常是来自不同的线程或不同的应用的消息。输入源异步传递事件到对应的处理程序和在线程关联的NSRunLoop对象调起runUntilDate:方法来退出事件处理。
Timer源:同步地传递事件,发生在每个定时器调用或周期性地调用。Timer源传递事件到他们的处理程序,但是不会调用run loop来退出处理。
这两种源在事件到达时都使用应用程序特定的处理程序来处理事件。
如下图所示,展示了run loop和不同的源的概述结构。
除了处理输入源之外,run loops还发出关于run loop行为的通知。我们可以注册成为run loop的观察者,就可以接收这些通知和使用它在线程上做一些额外处理。我们可以使用Core Foundation在对应的线程上注册成为run loop的观察者。
Run Loop Modes
Run Loop模式是一个监视输入源和定时器的集合和注册成为run loop的观察者的集合。每次要运行run loop,都需要显示或隐式地指定某种运行的mode。只有与这种指定的mode关联的源才会被监视和允许传递他们的事件,同样地,只有与这种模式关联的观察者都会收到run loop行为变化的通知。与其它模式想关联的源,直到随后在合适的模式通过循环后,都会接收到新的事件(比如,将timer加入run loop default模式下,当滚动时,timer不会收到回调,直到停止滚动回到default模式下)。
在我们的代码中,我们通过名称来唯一标识mode。在Cocoa和Core Foundation中都定义了default模式和几个常用的模式,都是通过字符串名称来指定。我们也可以自定义模式,但是我们需要手动添加至少一个input source/timers/observers。
我们可以通过使用mode来过滤掉我们不希望接收到来自不想要的通过run loop的源。大部分情况下,我们都是使用系统定义的default模式。对于子线程,我们可以使用自定义模式在关键性操作时阻止低优先级的源传递事件。
注意:Modes是通过事件源来区分,而不是事件类型来区分。比如说,我们不能使用mode来匹配只有mouse-down事件或者只有键盘事件。我们可以使用modes来监听不同系统的端口,临时挂起定时器,甚至改变正在被监视的sources和run loop观察者。
Input Sources
输入源异步传递事件到你的线程。事件的源由输入源的类型来决定,也就是两种源中的其中一种:
Port-based:基于端口号的输入源监听应用程序的Mach端口。
Custom Input Sources:自定义输入源监听自定义的事件源。
系统通常实现了这两种输入源。唯一的不同点是它们是如何被发出信号的。port-based源是由内核(kernel)自动发出信号,而custom sources必须手动从其它线程发出信号。
当我们创建输入源时,可以指定mode。Modes会影响任何时刻被监视的输入源。大部分情况下,我们都让run loop在default mode下运行,但是也可以指定自定义的mode。如果一个输入源不是当前所监视的model,它所产生的任何事件都会被保留直接进入正常的mode。
Port-Based Sources
Cocoa和Core Foundation提供了内建支持,可以使用与port相关的对象和函数来创建基于端口的输入源。举个例子,在Cocoa中永远不需要手动创建输入源。我们只需要简单地创建一个port对象和使用NSPort的方法。port对象为我们处理所需要的输入源的创建和配置。
在Core Foundation中,我们必须手动创建port和source。在这两种情况下,我们可以使用与port opaque type关联的函数(CFMessagePortRef, or CFSocketRef) 来创建合适的对象。
Custom Input Sources
在Core Foundation中,要创建自定义输入源,我们必须使用与CFRunLoopSourceRef关联的函数。我们配置自定义输入源可以使用几个回调函数。Core Foundation会在不同点回调这些函数来配置source,处理任何到达的事件和销毁已从run loop移除的source。
除了定义在事件到达时自定义源的行为之外,我们也必须定义事件传递机制。这部分源运行在单独的线程,负责提供输入源的数据,当数据准备好可以处理时,signaling(通知相关线程)这个消息。事件传递机制是我们自己来决定,但是不需要过于复杂。
Cocoa Perform Selector Source
除了基于端口的源之外,Cocoa还定义了自定义输入源允许我们在任意线程上执行selector。就像port-based源一样,执行selector请求会在目标线程上序列化,以减少在同一个线程中出现多个方法同步执行的问题。与port-based源不同的是,执行selector源在执行完毕后会自动将自己从run loop中移除。
当执行在其它线程执行selector时,目标线程必须要有运行的run loop。当我们创建线程时,这意味着直到启动了run loop都会显式地执行selector代码。
Run Loop每次经过一个循环,就会处理队列中所有的selector,而不仅仅是处理一个。
Timer Sources
Timer源在未来设定的时间会同步地传递事件到你的线程。Timers是线程通知自己去做一些事情的一种方式。比如说,搜索框可以使用定时器来初始化在一定时间就自动搜索,以便提供更多地联想词给用户。
尽管它发送基于时间的通知,但定时器并不是一种实时的机制。像输入源一样,定时器只有与run loop的mode一样才会发送通知。如果timer在run loop中并不是所被监视的mode,它不会触发定时器,直到run loop的mode与timer所支持的mode一样。
同样地,如果run loop正在处理中,timer已经fire了,这时候会被中断,直到下一次通过run loop才会调志处理程序。如果run loop已经不再运行了,则timer永远不会再fire。
我们可以配置timer只产生事件一次或者重复产生。重复的timer会自动根据调度的firing time自动调度,而不是真实的firing time。比如说,如果一个timer在特定的时间调度,然后每5秒重复一次。如果firing time被延迟导致缺少一或多次调用,那么timer在缺失的周期中只会调用一次。
Run Loop Observers
与sources在适当时机异步或同步发出事件不同,observers在run loop本身执行期间,会在特定的地方发出。你可能需要到run loop observers去准备线程处理特定的事件或者在进入睡眠之前。我们可以通过以下事件来关联run loop observers:
进入run loop
run loop将要处理timer
run loop将要处理输入源
run loop将要进入睡眠
run loop被唤醒,但是还没有处理事件
退出run loop
我们可以通过Core Foundation来添加run loop observers。要创建run loop observer,可以通过CFRunLoopObserverRef来创建新的实例。这个类型会跟踪你所定义的回调函数和所感兴趣的活动。
与timers类型,run-loop observers可以使用一次或者重复多次。一次性的observer会在fire之后自动从run loop移除,而重复性的observer会继续持有。
The Run Loop Sequence Of Events
本小节讲的是RunLoop事件顺序。每次运行它,你的线程的run loop处理待处理的事件和给所有attached observers发出通知。处理的顺序如下:
通知observers run loop已经进入
通知observers timers准备要fire
通知observers有不是基于port-based的输入源即将要fire
fire任何已经准备好的non-port-based输入源
如果port-based输入源准备好且等待fire,则立即处理这个事件。然后进入步骤9
通知observers线程即将进入睡眠
让线程进入睡眠,直到以下任何一种事件到达:
port-based输入源有事件到达
timer fire
run loop超时
run loop被显式唤醒
通知observers线程被唤醒
处理待处理的事件:
如果用户定义的timer fired了,处理timer事件并重新启动循环。进入步骤2
如果输入源fired了,则传递事件
如果run loop被显式唤醒,但是又未超时,则重启循环,进入步骤2
通知observers run loop退出
由于observer对timer和输入源的通知会在事件真正发生之前被传递,这样就产生了间隙。如果这个间隙是很关键的,那么我们可以通过使用sleep和awake-from-sleep通知来帮助我们纠正这个时间间隔问题。
什么时候应该使用run loop呢?
只有当我们需要创建子线程的时候,才会需要到显示地运行run loop。应用程序的主线程的run loop是应用启动的基础任务,在启动时就会自动启动run loop。所以我们不需要手动启动主线程的run loop。
对于子线程,我们需要确定线程是否需要run loop,如果需要,则配置它并启动它。我们并不问题需要启动run loop的。比如说,如果我们开一个子线程去执行一些长时间的和预先决定的任务,我们可能不需要启动run loop。Run loop是用于那么需要在线程中有更多地交互的场景。比如说,我们会在下面的任何一种场景中需要开启run loop:
使用端口源或者自定义输入源与其它线程通信
在线程中使用定时器
使用Cocoa中的任何performSelector…方法
保持线程来执行周期性的任务
Using Run Loop Objects
Run Loop对象给添加输入源、定时器和观察者到run loop提供了主接口。每个线程都有一个单独的run loop与之关联(对于子线程,若没有调用过任何获取run loop的方法是不会有run loop的,只有调用过,才会创建或者直接使用)。
在Cocoa中,通过NSRunLoop来创建实例,在low-level应用中,可以使用CFRunLoopRef类型,它是指针。
Getting A Run Loop Object
通过以下两种方式来获取run loop对象:
在Cocoa中,使用[NSRunLoop currentRunLoop]获取
使用CFRunLoopGetCurrent()函数获取
配置RunLoop
在子线程运行run loop之前,你必须至少添加一种输入源或者定时器。如果run loop没有任何的源需要监视,它就会立刻退出。
除了添加sources之外,你还可以添加观察者来检测runloop不同的执行状态。要添加观察者,可以使用CFRunLoopObserverRef指针类型和使用CFRunLoopAddObserver函数来添加到run loop中。我们只能通过Core Foundation来创建run loop观察者,即使是Cocoa应用。
下面这段代码展示主线程如何添加观察者到run loop以及如何创建run loop观察者:
Starting the Run Loop
只有子线程才有可能需要启动run loop。Run loop必须至少有一种输入源或者timer源来监视。如果没有任何源,则run loop会退出。
下面的几种方式可以启动run loop:
无条件地:无条件进入run loop是最简单的方式,但也是最不希望这么做的,因为这样会导致run loop会进入永久地循环。可以添加、删除输入源和timer源,但是只能通过kill掉run loop才能停止。而且还不能使用自定义mode。
限时:与无条件运行run loop不同,最好是给run loop添加一个超时时间。
在特定的mode:除了添加超时时间,还可以指定mode。
Exiting the Run Loop
有两种方法使run loop在处理事件之前,退出run loop:
给run loop设定超时时间
告诉run loop要stop
设定超时时间是比较推荐的。我们可以通过CFRunLoopStop函数来停止run loop。
Thread Safety and Run Loop Objects
Core Foundation中的Run Loop API是线程安全的(以CF开头的API),而Cocoa中的NSRunLoop不是线程安全的。
Configuring Run Loop Sources
下面是展示如何配置不同类型的输入源。
Defining a Custom Input Source
创建自定义输入源涉及到以下部分:
想要处理的输入源的信息
让感兴趣的客户端知道如何联系输入源的调度程序
执行任何客户端发送的请求处理程序
使输入源失效的取消程序
二、举例说明Runloop的优点。
一般情况下,当我们使用NSRunLoop的时候,代码如下所示:
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDate distantFuture]];
} while (!done);
在上面的代码中,参数done为NO的时候,当前runloop会一直接收处理其他输入源,处理输入源之后会再回到runloop中等待其他的输入源;除非done为NO,否则当前流程一直再runloop中。
如下面的代码片段所示,有三个按钮,分别对应如下三个action消息,buttonNormalThreadTestPressed,buttonRunloopPressed,buttonTestPressed。
buttonNormalThreadTestPressed:启动一个线程,在while循环中等待线程执行完再接着往下运行。
buttonRunloopPressed:启动一个线程,使用runloop,等待线程执行完再接着往下运行。
buttonTestPressed:仅仅打印两条日志,用来测试UI是否能立即响应的。
在本测试中,待程序运行后,做如下操作对比:
1、点击buttonNormalThreadTestPressed,然后立刻点击buttonTestPressed,查看日志输出。
2、待1完成后,点击buttonRunloopPressed,然后立刻点击buttonTestPressed,查看日志输出,跟1的日志做对比,即可以发现步骤2即使线程没有完成,在runloop等待过程中,界面仍然能够响应。
BOOLthreadProcess1Finished =NO;
-(void)threadProce1{
NSLog(@"Enter threadProce1.");
for(inti=0; i<5;i++) {
NSLog(@"InthreadProce1 count = %d.", i);
sleep(1);
}
threadProcess1Finished=YES;
NSLog(@"Exit threadProce1.");
}
BOOLthreadProcess2Finished =NO;
-(void)threadProce2{
NSLog(@"Enter threadProce2.");
for(inti=0; i<5;i++) {
NSLog(@"InthreadProce2 count = %d.", i);
sleep(1);
}
threadProcess2Finished=YES;
NSLog(@"Exit threadProce2.");
}
- (IBAction)buttonNormalThreadTestPressed:(UIButton*)sender {
NSLog(@"EnterbuttonNormalThreadTestPressed");
threadProcess1Finished=NO;
NSLog(@"Start a new thread.");
[NSThreaddetachNewThreadSelector:@selector(threadProce1)
toTarget:self
withObject:nil];
// 通常等待线程处理完后再继续操作的代码如下面的形式。
// 在等待线程threadProce1结束之前,调用buttonTestPressed,界面没有响应,直到threadProce1运行完,才打印buttonTestPressed里面的日志。
while(!threadProcess1Finished) {
[NSThreadsleepForTimeInterval: 0.5];
}
NSLog(@"ExitbuttonNormalThreadTestPressed");
}
- (IBAction)buttonRunloopPressed:(id)sender {
NSLog(@"Enter buttonRunloopPressed");
threadProcess2Finished=NO;
NSLog(@"Start a new thread.");
[NSThreaddetachNewThreadSelector:@selector(threadProce2)
toTarget:self
withObject:nil];
// 使用runloop,情况就不一样了。
// 在等待线程threadProce2结束之前,调用buttonTestPressed,界面立马响应,并打印buttonTestPressed里面的日志。
// 这就是runloop的神奇所在
while(!threadProcess2Finished) {
NSLog(@"Begin runloop");
[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopMode
beforeDate: [NSDatedistantFuture]];
NSLog(@"End runloop.");
}
NSLog(@"Exit buttonRunloopPressed");
}
- (IBAction)buttonTestPressed:(id)sender{
NSLog(@"Enter buttonTestPressed");
NSLog(@"Exit buttonTestPressed");
}
日志信息如下:
2013-04-07 14:25:22.829 Runloop[657:11303] EnterbuttonNormalThreadTestPressed
2013-04-07 14:25:22.830 Runloop[657:11303] Start a new thread.
2013-04-07 14:25:22.831 Runloop[657:1250f] Enter threadProce1.
2013-04-07 14:25:22.832 Runloop[657:1250f] In threadProce1 count = 0.
2013-04-07 14:25:23.833 Runloop[657:1250f] In threadProce1 count = 1.
2013-04-07 14:25:24.834 Runloop[657:1250f] In threadProce1 count = 2.
2013-04-07 14:25:25.835 Runloop[657:1250f] In threadProce1 count = 3.
2013-04-07 14:25:26.837 Runloop[657:1250f] In threadProce1 count = 4.
2013-04-07 14:25:27.839 Runloop[657:1250f] Exit threadProce1.
2013-04-07 14:25:27.840 Runloop[657:11303]ExitbuttonNormalThreadTestPressed
2013-04-07 14:25:27.841 Runloop[657:11303]EnterbuttonTestPressed
2013-04-07 14:25:27.842 Runloop[657:11303] Exit buttonTestPressed
2013-04-07 14:25:27.843 Runloop[657:11303] Enter buttonTestPressed
2013-04-07 14:25:27.844 Runloop[657:11303] Exit buttonTestPressed
2013-04-07 14:43:41.790 Runloop[657:11303] Enter buttonRunloopPressed
2013-04-07 14:43:41.790 Runloop[657:11303] Start a new thread.
2013-04-07 14:43:41.791 Runloop[657:11303] Begin runloop
2013-04-07 14:43:41.791 Runloop[657:14f0b] Enter threadProce2.
2013-04-07 14:43:41.792 Runloop[657:14f0b] In threadProce2 count = 0.
2013-04-07 14:43:42.542 Runloop[657:11303] End runloop.
2013-04-07 14:43:42.543 Runloop[657:11303] Begin runloop
2013-04-07 14:43:42.694 Runloop[657:11303]Enter buttonTestPressed
2013-04-07 14:43:42.694 Runloop[657:11303]Exit buttonTestPressed
2013-04-07 14:43:42.695 Runloop[657:11303] End runloop.
2013-04-07 14:43:42.696 Runloop[657:11303] Begin runloop
2013-04-07 14:43:42.793 Runloop[657:14f0b] In threadProce2 count = 1.
2013-04-07 14:43:43.326 Runloop[657:11303] End runloop.
2013-04-07 14:43:43.327 Runloop[657:11303] Begin runloop
2013-04-07 14:43:43.438 Runloop[657:11303]Enter buttonTestPressed
2013-04-07 14:43:43.438 Runloop[657:11303]Exit buttonTestPressed
2013-04-07 14:43:43.439 Runloop[657:11303] End runloop.
2013-04-07 14:43:43.440 Runloop[657:11303] Begin runloop
2013-04-07 14:43:43.795 Runloop[657:14f0b] In threadProce2 count = 2.
2013-04-07 14:43:44.797 Runloop[657:14f0b] In threadProce2 count = 3.
2013-04-07 14:43:45.798 Runloop[657:14f0b] In threadProce2 count = 4.
2013-04-07 14:43:46.800 Runloop[657:14f0b] Exit threadProce2.
三、Runloop简单实例:
- (void)viewDidLoad
{
[superviewDidLoad];
// Doany additional setup after loading the view, typically from a nib.
[NSThreaddetachNewThreadSelector:@selector(newThreadProcess)
toTarget:self
withObject:nil];
}
- (void)newThreadProcess
{
@autoreleasepool{
////获得当前thread的Runloop
NSRunLoop* myRunLoop = [NSRunLoopcurrentRunLoop];
//设置Run loop observer的运行环境
CFRunLoopObserverContextcontext = {0,self,NULL,NULL,NULL};
//创建Run loop observer对象
//第一个参数用于分配observer对象的内存
//第二个参数用以设置observer所要关注的事件,详见回调函数myRunLoopObserver中注释
//第三个参数用于标识该observer是在第一次进入runloop时执行还是每次进入run loop处理时均执行
//第四个参数用于设置该observer的优先级
//第五个参数用于设置该observer的回调函数
//第六个参数用于设置该observer的运行环境
CFRunLoopObserverRefobserver =CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0, &myRunLoopObserver, &context);
if(observer)
{
//将Cocoa的NSRunLoop类型转换成CoreFoundation的CFRunLoopRef类型
CFRunLoopRefcfRunLoop = [myRunLoopgetCFRunLoop];
//将新建的observer加入到当前thread的runloop
CFRunLoopAddObserver(cfRunLoop, observer,kCFRunLoopDefaultMode);
}
//
[NSTimerscheduledTimerWithTimeInterval:1
target:self
selector:@selector(timerProcess)
userInfo:nil
repeats:YES];
NSIntegerloopCount =2;
do{
//启动当前thread的loop直到所指定的时间到达,在loop运行时,runloop会处理所有来自与该run loop联系的inputsource的数据
//对于本例与当前run loop联系的inputsource只有一个Timer类型的source。
//该Timer每隔1秒发送触发事件给runloop,run loop检测到该事件时会调用相应的处理方法。
//由于在run loop添加了observer且设置observer对所有的runloop行为都感兴趣。
//当调用runUnitDate方法时,observer检测到runloop启动并进入循环,observer会调用其回调函数,第二个参数所传递的行为是kCFRunLoopEntry。
//observer检测到runloop的其它行为并调用回调函数的操作与上面的描述相类似。
[myRunLooprunUntilDate:[NSDatedateWithTimeIntervalSinceNow:5.0]];
//当run loop的运行时间到达时,会退出当前的runloop。observer同样会检测到runloop的退出行为并调用其回调函数,第二个参数所传递的行为是kCFRunLoopExit。
loopCount--;
}while(loopCount);
}
}
voidmyRunLoopObserver(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity,void*info)
{
switch(activity) {
//The entrance of the run loop, beforeentering the event processing loop.
//This activity occurs once for each callto CFRunLoopRun and CFRunLoopRunInMode
casekCFRunLoopEntry:
NSLog(@"run loop entry");
break;
//Inside the event processing loop beforeany timers are processed
casekCFRunLoopBeforeTimers:
NSLog(@"run loop before timers");
break;
//Inside the event processing loop beforeany sources are processed
casekCFRunLoopBeforeSources:
NSLog(@"run loop before sources");
break;
//Inside the event processing loop beforethe run loop sleeps, waiting for a source or timer to fire.
//This activity does not occur ifCFRunLoopRunInMode is called with a timeout of 0 seconds.
//It also does not occur in a particulariteration of the event processing loop if a version 0 source fires
casekCFRunLoopBeforeWaiting:
NSLog(@"run loop before waiting");
break;
//Inside the event processing loop afterthe run loop wakes up, but before processing the event that woke it up.
//This activity occurs only if the run loopdid in fact go to sleep during the current loop
casekCFRunLoopAfterWaiting:
NSLog(@"run loop after waiting");
break;
//The exit of the run loop, after exitingthe event processing loop.
//This activity occurs once for each callto CFRunLoopRun and CFRunLoopRunInMode
casekCFRunLoopExit:
NSLog(@"run loop exit");
break;
/*
A combination of all the precedingstages
case kCFRunLoopAllActivities:
break;
*/
default:
break;
}
}
- (void)timerProcess{
for(inti=0; i<5; i++) {
NSLog(@"In timerProcess count = %d.", i);
sleep(1);
}
}
调试打印信息如下:
2012-12-18 09:51:14.174 Texta[645:14807] run loop entry
2012-12-18 09:51:14.175 Texta[645:14807] run loop before timers
2012-12-18 09:51:14.176 Texta[645:14807] run loop before sources
2012-12-18 09:51:14.177 Texta[645:14807] run loop before waiting
2012-12-18 09:51:15.174 Texta[645:14807] run loop after waiting
2012-12-18 09:51:15.176 Texta[645:14807] In timerProcess count = 0.
2012-12-18 09:51:16.178 Texta[645:14807] In timerProcess count = 1.
2012-12-18 09:51:17.181 Texta[645:14807] In timerProcess count = 2.
2012-12-18 09:51:18.183 Texta[645:14807] In timerProcess count = 3.
2012-12-18 09:51:19.185 Texta[645:14807] In timerProcess count = 4.
2012-12-18 09:51:20.187 Texta[645:14807] run loop exit
2012-12-18 09:51:20.189 Texta[645:14807] run loop entry
2012-12-18 09:51:20.190 Texta[645:14807] run loop before timers
2012-12-18 09:51:20.191 Texta[645:14807] run loop before sources
2012-12-18 09:51:20.191 Texta[645:14807] run loop before waiting
2012-12-18 09:51:21.174 Texta[645:14807] run loop after waiting
2012-12-18 09:51:21.176 Texta[645:14807] In timerProcess count = 0.
2012-12-18 09:51:22.178 Texta[645:14807] In timerProcess count = 1.
2012-12-18 09:51:23.181 Texta[645:14807] In timerProcess count = 2.
2012-12-18 09:51:24.183 Texta[645:14807] In timerProcess count = 3.
2012-12-18 09:51:25.185 Texta[645:14807] In timerProcess count = 4.
2012-12-18 09:51:26.187 Texta[645:14807] run loop exit
四、Runloop可以阻塞线程,等待其他线程执行后再执行。
比如:
BOOLStopFlag =NO;
- (void)viewDidLoad
{
[superviewDidLoad];
// Doany additional setup after loading the view, typically from a nib.
StopFlag=NO;
NSLog(@"Start a new thread.");
[NSThreaddetachNewThreadSelector:@selector(newThreadProc)
toTarget:self
withObject:nil];
while(!StopFlag) {
NSLog(@"Beginrunloop");
[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopMode
beforeDate: [NSDatedistantFuture]];
NSLog(@"Endrunloop.");
}
NSLog(@"OK");
}
-(void)newThreadProc{
NSLog(@"Enter newThreadProc.");
for(inti=0; i<10; i++) {
NSLog(@"InnewThreadProc count = %d.", i);
sleep(1);
}
StopFlag=YES;
NSLog(@"Exit newThreadProc.");
}
}
调试打印信息如下:
2012-12-18 08:50:34.220 Runloop[374:11303] Start a new thread.
2012-12-18 08:50:34.222 Runloop[374:11303] Begin runloop
2012-12-18 08:50:34.222 Runloop[374:14b03] Enter newThreadProc.
2012-12-18 08:50:34.223 Runloop[374:14b03] In newThreadProc count = 0.
2012-12-18 08:50:35.225 Runloop[374:14b03] In newThreadProc count = 1.
2012-12-18 08:50:36.228 Runloop[374:14b03] In newThreadProc count = 2.
2012-12-18 08:50:37.230 Runloop[374:14b03] In newThreadProc count = 3.
2012-12-18 08:50:38.233 Runloop[374:14b03] In newThreadProc count = 4.
2012-12-18 08:50:39.235 Runloop[374:14b03] In newThreadProc count = 5.
2012-12-18 08:50:40.237 Runloop[374:14b03] In newThreadProc count = 6.
2012-12-18 08:50:41.240 Runloop[374:14b03] In newThreadProc count = 7.
2012-12-18 08:50:42.242 Runloop[374:14b03] In newThreadProc count = 8.
2012-12-18 08:50:43.245 Runloop[374:14b03] In newThreadProc count = 9.
2012-12-18 08:50:44.247 Runloop[374:14b03] Exit newThreadProc.
2012-12-18 08:51:00.000 Runloop[374:11303] End runloop.
2012-12-18 08:51:00.001 Runloop[374:11303] OK
从调试打印信息可以看到,while循环后执行的语句会在很长时间后才被执行。因为,改变变量StopFlag的值,runloop对象根本不知道,runloop在这个时候未被唤醒。有其他事件在某个时点唤醒了主线程,这才结束了while循环,但延缓的时长总是不定的。。
将代码稍微修改一下:
[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopMode
beforeDate:[NSDatedateWithTimeIntervalSinceNow:1]];
缩短runloop的休眠时间,看起来解决了上面出现的问题。
但这样会导致runloop被经常性的唤醒,违背了runloop的设计初衷。runloop的目的就死让你的线程在有工作的时候忙于工作,而没工作的时候处于休眠状态。
最后,看下下面正确的写法:
BOOLStopFlag =NO;
- (void)viewDidLoad
{
[superviewDidLoad];
// Doany additional setup after loading the view, typically from a nib.
StopFlag=NO;
NSLog(@"Start a new thread.");
[NSThreaddetachNewThreadSelector:@selector(newThreadProc)
toTarget:self
withObject:nil];
while(!StopFlag) {
NSLog(@"Beginrunloop");
[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopMode
beforeDate: [NSDatedistantFuture]];
NSLog(@"Endrunloop.");
}
NSLog(@"OK");
}
-(void)newThreadProc{
NSLog(@"Enter newThreadProc.");
for(inti=0; i<10; i++) {
NSLog(@"InnewThreadProc count = %d.", i);
sleep(1);
}
[selfperformSelectorOnMainThread:@selector(setEnd)
withObject:nil
waitUntilDone:NO];
NSLog(@"Exit newThreadProc.");
}
-(void)setEnd{
StopFlag=YES;
}
调试打印信息如下:
2012-12-18 09:05:17.161 Runloop[410:11303] Start a new thread.
2012-12-18 09:05:17.163 Runloop[410:14a03] Enter newThreadProc.
2012-12-18 09:05:17.164 Runloop[410:14a03] In newThreadProc count = 0.
2012-12-18 09:05:17.165 Runloop[410:11303] Begin runloop
2012-12-18 09:05:18.166 Runloop[410:14a03] In newThreadProc count = 1.
2012-12-18 09:05:19.168 Runloop[410:14a03] In newThreadProc count = 2.
2012-12-18 09:05:20.171 Runloop[410:14a03] In newThreadProc count = 3.
2012-12-18 09:05:21.173 Runloop[410:14a03] In newThreadProc count = 4.
2012-12-18 09:05:22.175 Runloop[410:14a03] In newThreadProc count = 5.
2012-12-18 09:05:23.178 Runloop[410:14a03] In newThreadProc count = 6.
2012-12-18 09:05:24.180 Runloop[410:14a03] In newThreadProc count = 7.
2012-12-18 09:05:25.182 Runloop[410:14a03] In newThreadProc count = 8.
2012-12-18 09:05:26.185 Runloop[410:14a03] In newThreadProc count = 9.
2012-12-18 09:05:27.188 Runloop[410:14a03] Exit newThreadProc.
2012-12-18 09:05:27.188 Runloop[410:11303] End runloop.
2012-12-18 09:05:27.189 Runloop[410:11303] OK
把直接设置变量,改为向主线程发送消息,唤醒runloop,延时问题解决。