IOS RunLoop 之我解

来源:互联网 发布:怎么看淘宝店铺的地址 编辑:程序博客网 时间:2024/05/16 15:36
Run Loops
一、什么是Run Loops?
    一个run loop就是一个事件处理循环,用来不停的调度工作和处理输入事件。也可以理解更好的抽象和封装的消息处理模式,他响应消息,并且将消息交给对应的事件处理程序进行处理。(如触屏事件:当用户点击屏幕,run loop被唤醒然后调用touchsBegan:withEvent 进行处理。)
    其实runloop做为一种事件处理机制,类似一个车间的主任(不知道这种比喻是否恰当),这个主任他负责处理这个车间流水线上面发生特定类型事件的处理(这里的特定事件就是runMode)这个事件可以包括安全事件,机械故障等等。该主任处理时间的传统方式可以是每隔一分钟巡逻生产线一次(对应的是cpu空转轮询消息队列的方式),这种方式比较耗费工人体力(cpu资源,电量),当发现有问题发生,他就找对应的工人去处理,这里的工人对应于时间的处理函数;还有一种方式就是主任平时都在睡觉打麻将,当生产线发生问题的时候,如果是属于他的职责,系统就直接给他发送一条短信通知他,他收到之后再通知对应的工人去处理,上面那个十秒钟(这个是可以更改的)就是如果十秒内没有消息通知过来,主任才会去车间巡逻一次,但是这十秒由于主任是没有收到事件处理消息的,所以他通常是到了车间就走了(对应runLoop启动就结束,没有事件处理)。
   对于多线程开发而言,有两种情况:
     1、 不需要太多的交互,但是需要较大的运算或者是比较耗时的操作(文件读写);
     2、正好相反,需要较多的交互和循环处理事件(如下载文件的时候,定时器事件);
第二种情况下,使用run loop将表现得更好,因为在没有事件处理的时候,线程将处于休眠状态,有事件请求到来时,启动run loop唤醒线程,这样更加高效和省电。
二、run loop接收的输入事件源
    run loop输入事件来自两种不同的输入源:
     1、输入源(input source);
     2、定时源(time source);
  输入源包括:基于端口的输入源和自定义的输入源。基于端口的输入源监听程序的相应端口(触屏事件、网络数据等),该类源有内核自动发送;自定义输入源由人工通过其他线程发送(performSelecter方法):
定时源主要就是定时器相关的输入源。
三、Run Loop 模式
run loop的模式就是输入事件源的一些集合,这些集合包括了cocoa本身自定义的,你也可以自己定义需要处理的集合;
  1、NSDefaultRunLoopMode:包括了默认的大多数操作,通常情况下采用该模式就足够了。
  2、NSConnectionRelyMode:处理NSConnection的源,系统内部使用,用户不需要使用。
  3、NSModalPanelRunLoopMode:modal panel相关.
  4、NSEventTrackingRunLoopMode:在拖动loop或其他user interface tracking loops时处于此种模式下,在此模式下会限制输入事件的处理。例如,当手指按住UITableView拖动时就会处于此模式。
  5、NSRunLoopCommonMode:包括了:1、3、4。
如果对run loop设定了对应的集合,run loop就只会处理该集合内的事件源,而不处理集合外的事件源。

NSTimer的例子:
在一个UITableViewController中启动一个0.2s的循环定时器,在定时器到期时更新一个计数器,并显示在label上。

-(void)viewDidLoad
{
    label = [[UILabel alloc]initWithFrame:CGRectMake(10, 100, 100, 50)];
    [self.view addSubview:label];
    [label release];
    count = 0;
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: 1
                                                      target: self
                                                    selector: @selector(incrementCounter:)
                                                    userInfo: nil
                                                     repeats: YES];
}


- (void)incrementCounter:(NSTimer *)theTimer
{
    count++;
    label.text = [NSString stringWithFormat:@"%d",count];
}
但当你拖动或按住tableView时,label上的数字不再更新,当你手指离开时,label上的数字继续更新。
这是由于当你拖动UItableView时,当前线程run loop处于UIEventTrackingRunLoopMode模式,在这种模式下,不处理定时器事件,即定时器无法fire,label上的数字也就无法更新。
这个示例在正常情况下lable上的数字会定时变化。
解决方法,一种方法是在另外的线程中处理定时器事件,可把Timer加入到NSOperation中在另一个线程中调度;还有一种方法时修改Timer运行的run loop模式,将其加入到UITrackingRunLoopMode模式或NSRunLoopCommonModes模式中。

[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

修改NSURLConnection的运行模式可使用scheduleInRunLoop:forMode:方法。

NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15];
NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]autorelease];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];

四、使用run loop
只有你的程序创建辅助线程的时候才需要显示运行一个run loop,但是如果你的辅助线程没有事件源需要处理,只是简单的进行计算和耗时操作也是不需要创建run loop的。
run loop是程序主线程的关键部分,IOS在程序中的UIApplication的run 方法中在程序正常启动的时候就会启动程序的主循环。
对于辅助线程而言页需要判断一个run loop是不是必须的,run loop 一般需要在更多交互时才需要使用,比如以下情况:
        l 使用端口或自定义输入源来和其他线程通信
        l 使用线程的定时器
        l Cocoa中使用任何performSelector...的方法
        l 使线程周期性工作
test:4-1

//on mainThread

-(void)testRunLoop

{

   NSLog(@"start new thread!!!");

    [selfperformSelector:@selector(runOnNewThread)withObject:selfafterDelay:2];

  

}

 

-(void)runOnNewThread

{

   NSAutoreleasePool * pool = [[NSAutoreleasePoolalloc] init];

    [selfperformSelector:@selector(delayRuning)withObject:nilafterDelay:0.0];

    [pool release];

}


-(void)delayRuning

{

   NSLog(@"new thread delay running!!!");

}

代码的执行结果会是怎样?

start new thread!!!
new thread delay running!!!
test:4-2

//on mainThread

-(void)testRunLoop

{

   NSLog(@"start new thread!!!");

    [NSThreaddetachNewThreadSelector:@selector(runOnNewThread)toTarget:selfwithObject:nil];

}

 

-(void)runOnNewThread

{

   NSAutoreleasePool * pool = [[NSAutoreleasePoolalloc] init];

    [selfperformSelector:@selector(delayRuning)withObject:nilafterDelay:0.0];

    [pool release];

}


-(void)delayRuning

{

   NSLog(@"new thread delay running!!!");

}

这段程序执行的结果会是怎样?

start new thread!!!

代码NSLog(@"new thread delay running!!!");将不会被执行,原因是在辅助线程中没有显示的运行一个run loop;

怎样更改代码NSLog(@"new thread delay running!!!")才会被执行?

test:4-3

//on mainThread

-(void)testRunLoop

{

   NSLog(@"start new thread!!!");

    [NSThreaddetachNewThreadSelector:@selector(runOnNewThread)toTarget:selfwithObject:nil];

}

 

-(void)runOnNewThread

{

   NSAutoreleasePool * pool = [[NSAutoreleasePoolalloc] init];

    [selfperformSelector:@selector(delayRuning)withObject:nilafterDelay:0.0];

    [[NSRunLoopcurrentRunLoop] run];

    //或者:

    [[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopModebeforeDate:[NSDatedistantFuture]];  

    [pool release];

}


-(void)delayRuning

{

   NSLog(@"new thread delay running!!!");

}

通过显示运行辅助线程的run loop,辅助线程中的performSelecter将得到执行。
[[NSRunLoopcurrentRunLoop] run];和[[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDatedistantFuture]];都能显示的运行辅助线程中的run loop。但是这两个方法都会导致子线程的run loop不会退出,但是这个地方不会,因为子线程没有
需要监控的源。

下面讲解一下[[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDatedistantFuture]];这个方法
这个方法的含义是:run loop运作直到监控源的某一事件到达或者规定的事件已经到期;如果事件到达消息会传递给相应的处理程序来处理,然后run loop退出。
如果事件没有到达,当时间到期(也就是beforeDate:参数时间的时候)也会启动run loop,然后有事件就处理,没有就退出。这个地方通常会直接退出。如果成功启动
(不管Run loop有没有执行事件处理)run loop该方法返回YES,如果run loop启动失败,返回NO.

下面是使用该方法的几个示例:
test:4-4

//on mainThread

-(void)testRunLoop

{

   _stopRunning = NO;

   _startInterval = [NSDatetimeIntervalSinceReferenceDate];

   NSLog(@"start new thread!!!");

    [NSThreaddetachNewThreadSelector:@selector(runOnNewThread)toTarget:selfwithObject:nil];

   

   while (!_stopRunning)

    {

       NSLog(@"the loop begin time is:  %f  s",[NSDatetimeIntervalSinceReferenceDate] -  _startInterval);

       BOOL result = [[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDate       dateWithTimeIntervalSinceNow:10]];

       NSLog(@"the loop end time is:  %f  s",[NSDatetimeIntervalSinceReferenceDate] -  _startInterval);  

       

    }

   NSLog(@"end time is:  %f  s",[NSDatetimeIntervalSinceReferenceDate] -  _startInterval);

}

  

-(void)runOnNewThread

{

   NSAutoreleasePool * pool = [[NSAutoreleasePoolalloc] init];

   NSLog(@"newThreadRuning begin time:  %f  s",[NSDatetimeIntervalSinceReferenceDate] - _startInterval);

   _stopRunning = YES;

   NSLog(@"newThreadRuning end time:  %f  s",[NSDatetimeIntervalSinceReferenceDate] - _startInterval);

    [pool release];

}


这段代码的运行结果会是怎样?上面的循环会不会一直不停的跑?

start new thread!!!the loop begin time is:  0.012369  s

newThreadRuning begin time:  0.012445  s

newThreadRuning end time:  0.013623  s


the loop end time is:  10.013487  s

end time is:  10.013777  s

为什么在子线程改变_stopRunning变量之后主线程的循环没有立即退出,而循环没有不停的跑。
这个地方循环之所以不会不停的跑,就是前面讲到的run loop的原理,run loop在有事件到来的时候就会被唤醒,没有事件到来的时候就会一直休眠达到节约资源的目的。
这个地方循环进来之后run loop就会休眠,从而将线程暂停。直到10秒之后,启动run loop。
如果我面将上面的代码更改一下:
test:4-5

//on mainThread

-(void)testRunLoop

{

   _stopRunning = NO;

   _startInterval = [NSDatetimeIntervalSinceReferenceDate];

   NSLog(@"start new thread!!!");

    [NSThreaddetachNewThreadSelector:@selector(runOnNewThread)toTarget:selfwithObject:nil];

   

   while (!_stopRunning)

    {

       NSLog(@"the loop begin time is:  %f  s",[NSDatetimeIntervalSinceReferenceDate] -  _startInterval);

       BOOL result = [[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDatedateWithTimeIntervalSinceNow:10]];

       NSLog(@"the loop end time is:  %f  s",[NSDatetimeIntervalSinceReferenceDate] -  _startInterval);  

       

    }

   NSLog(@"end time is:  %f  s",[NSDatetimeIntervalSinceReferenceDate] -  _startInterval);

}

-(void)runOnNewThread

{

   NSAutoreleasePool * pool = [[NSAutoreleasePoolalloc] init];

   NSLog(@"newThreadRuning begin time:  %f  s",[NSDatetimeIntervalSinceReferenceDate] - _startInterval);

    [selfperformSelectorOnMainThread:@selector(setRuning)withObject:nilwaitUntilDone:NO];

   NSLog(@"newThreadRuning end time:  %f  s",[NSDatetimeIntervalSinceReferenceDate] - _startInterval);

    [pool release];
}

   

-(void)setRuning

{

   _stopRunning = YES;

}

上面这个代码的执行结果又会是怎么样?

start new thread!!!

the loop begin time is:  0.000947  s

newThreadRuning begin time:  0.001023  s

newThreadRuning end time:  0.001267  s

the loop end time is:  0.001327  s

end time is:  0.001542  s

这时当子线程改变之后主线程立即就退出了,原因就是performSelectorOnMainThread:方法给主线程发送了事件,因此主线程启动了run loop唤醒了线程。这个地方如果你做的是ui相关的test程序的话,在test:4-4的时候你如果随意点击一下ui,也会引发循环的退出,因为点击事件同样触发了主线程的run loop。方法performSelectorOnMainThread:中的waitUntilDone:的作用是否等待@selector方法执行的完成。
将:
[selfperformSelectorOnMainThread:@selector(setRuning)withObject:nilwaitUntilDone:NO];
改为:
[selfperformSelectorOnMainThread:@selector(setRuning)withObject:nilwaitUntilDone:YES];
然后在函数setRunning中打断点得到的调用栈如下:
从上面的调用栈可看出,通过performSelectorOnMainThread抛到主线程之后,主线程启动run loop然后运行函数setRuning;
test4-6:

//on mainThread

-(void)testRunLoop

{

   _stopRunning = NO;

   _startInterval = [NSDatetimeIntervalSinceReferenceDate];

   NSLog(@"start new thread!!!");

    [NSThreaddetachNewThreadSelector:@selector(runOnNewThread)toTarget:selfwithObject:nil];

   NSLog(@"end time is:  %f  s",[NSDatetimeIntervalSinceReferenceDate] -  _startInterval);

}

-(void)runOnNewThread

{

   NSAutoreleasePool * pool = [[NSAutoreleasePoolalloc] init];

   while (!_stopRunning)

    {

        [[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDatedistantFuture]];

        NSLog(@"runing");

    }

 

   NSLog(@"newThreadRuning end ");

    [poolrelease];  

}

这段代码的执行结果会是怎么样?

NSLog(@"runing");这段代码能够得到执行吗?

这个地方如果没有别的线程再向这个新的线程发送事件的话,这个线程将会陷入死循环,并且
[[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDatedistantFuture]];这段代码将不会“暂停”
线程,这是为什么呢?因为Run loop在没有任何监控源的状况下是会启动就退出的。

test: 4-7

//on mainThread

-(void)testRunLoop

{

   _stopRunning = NO;

   _startInterval = [NSDatetimeIntervalSinceReferenceDate];

   NSLog(@"start new thread!!!");

    [NSThreaddetachNewThreadSelector:@selector(runOnNewThread)toTarget:selfwithObject:nil];

   NSLog(@"end time is:  %f  s",[NSDatetimeIntervalSinceReferenceDate] -  _startInterval);

}

-(void)runOnNewThread

{

   NSAutoreleasePool *pool = [[NSAutoreleasePoolalloc] init];


    [NSTimerscheduledTimerWithTimeInterval:10target:selfselector:@selector(repeatRunning)userInfo:nilrepeats:YES];

    while (!_stopRunning)

    {

        [[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDatedistantFuture]];

        NSLog(@"runing");

    }


   NSLog(@"newThreadRuning end");

    [pool release];

}

-(void)repeatRunning

{

   NSLog(@"new thread repeat Running!!!");

}

这段代码的执行结果又会是什么样的?


这段代码中:

NSLog(@"runing");
将永远不会执行,因为run loop的监控的输入源定时器没有退出,因此run loop将不会停止。

这个地方讲到run loop的退出,run loop有两种方法可以退出:
  1、给run loop设置超时时间;也就是在方法beforeDate后面设置一个时间。
  2、通知run loop停止;使用显示的CFRunLoopStop来显示停止run loop。
虽然通过移除run loop的输入源和定时器也是可能导致Run loop退出,就像上面将repeats:NO,run loop在执行完一次方法repeatRunning之后就会退出。但是这个并不可靠,因为你只考虑到自己的代码,未必考虑到系统的输入源并不一定移除,从而导致run loop不能退出。所以推荐使用超时的方法设置Run loop。

下面列举一个将NSURLConnection加入到run loop中。

  NSURL *url=[[NSURLalloc] initWithString:@"http:www.qq.com"];   

 NSMutableURLRequest *request=[[NSMutableURLRequestalloc] initWithURL:url];  

 NSURLConnection *connect=[[NSURLConnectionalloc] initWithRequest:requestdelegate:selfstartImmediately:YES];

 

  NSPort *port=[NSPortport];

  NSRunLoop *runloop=[NSRunLoopcurrentRunLoop];  

  [runloop addPort:port forMode:NSDefaultRunLoopMode];

  

  [connect scheduleInRunLoop:runloop forMode:NSDefaultRunLoopMode];  

  [connect start];  

  [runloop run];

原创粉丝点击