runloop实现原理初解 和 使用runloop阻塞线程

现在说说runloop为何会成为cocoa开发中迷惑的点。因为很多新手没有从动态角度看它。 首先回想一下第2点介绍的runtime的概念。 接着我出一个题思考一下。 
 
现在我有一个程序片段如下: 
复制代码
  1. - (void)myThread:(id)sender
  2. {
  3.     NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
  4.     while (TRUE) {
  5.         
  6.         //do some jobs
  7.        //break in some condition
  8.         
  9.         usleep(10000);
  10.         
  11.         [pool drain];
  12.     }
  13.     
  14.     [pool release];
  15. }
 
现在要求,做某些设计,使得当这个线程运行的同时,还可以从其它线程里往它里面随意增加或去掉不同的计算任务。 这,就是NSRunloop的最原始的开发初衷。让一个线程的计算任务更加灵活。 这个功能在c, c++里也许可以做到但是非常难,最主要的是因为语言能力的限制,以前的程序员很少这么去思考。 
 
好,现在我们对上面代码做一个非常简单的进化: 
 
复制代码
  1. NSMutableArray *targetQueue;
  2. NSMutableArray *actionQueue;

  3. - (void)myThread:(id)sender
  4. {
  5.     NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
  6.     while (TRUE) {
  7.         
  8.         //do some jobs
  9.         //break in some condition
  10.         int n=[targetQueue count];
  11.         assert(n==[actionQueue count]);
  12.         for(int i=0;i<n;i++){
  13.             id target=[targetQueue objectAtIndex:i];
  14.             SEL action=NSSelectorFromString([actionQueue objectAtIndex:i]);
  15.             if ([target respondsToSelector:action]) {
  16.                 [target performSelector:action withObject:nil];
  17.             }
  18.         }
  19.                 
  20.         usleep(10000);
  21.         
  22.         [pool drain];
  23.     }
  24.     
  25.     [pool release];
  26. }
 
注意,这里没有做线程安全处理,记住Mutable container is not thread safe. 
这个简单的扩展,让我们看到了如何利用runtime能力让线程灵活起来。当我们从另外线程向targetQueue和actionQueue同时加入对象和方法时候,这个线程函数就有了执行一个额外代码的能力。 
 
但,有人会问,哪里有runloop? 那个是 nsrunloop? 看不出来啊。 
复制代码
  1. while (TRUE) {
  2. //break in some condition
  3. }
 
一个线程内这个结构就叫线程的runloop,   它和NSRunloop这个类虽然名字很像,但完全不是一个东西。以前在使用静态语言开始时候,程序员没有什么迷惑,因为没有NSRunloop这个东西。 我接着来说,这个NSRunloop是如何来得。 
 
第二段扩展代码里面确实没有NSRunloop这个玩意儿,我们接着做第3次改进。 这次我们的目的是把其中动态部分抽象出来。 
 
复制代码

  1. @interface MyNSTimer : NSObject
  2. {
  3.   id target;
  4.   SEL action;
  5.   float interval;
  6.   CFAbsoluteTime lasttime;
  7. }
  8. - (void)invoke;
  9. @end

  10. @implementation MyNSTimer
  11. - (void)invoke;
  12. {
  13.   if ([target respondsToSelector:action]) {
  14.         [target performSelector:action withObject:nil];
  15.     }
  16. }
  17. @end
 
 
 
复制代码
  1. @interface MyNSRunloop : NSObject
  2. {
  3.   NSMutableArray *timerQueue;
  4. }
  5. - (void)addTimer:(MyNSTimer*)t;
  6. - (void)executeOnce;
  7. @end

  8. @implementation MyNSRunloop
  9. - (void)addTimer:(MyNSTimer*)t;
  10. {
  11.   @synchronized(timerQueue){
  12.       [timerQueue addObject:t];
  13.     }
  14. }
  15. - (void)executeOnce;
  16. {
  17.   CFAbsoluteTime currentTime=CFAbsoluteTimeGetCurrent();
  18.   @synchronized(timerQueue){
  19.       for(MyNSTimer *t in timerQueue){
  20.           if(currentTime-t.lasttime>t.interval){
  21.               t.lasttime=currentTime;
  22.                   [t invoke];
  23.           }
  24.       }
  25.   }
  26. }
  27. @end
 
 
复制代码
  1. @interface MyNSThread : NSObject
  2. {
  3.   MyNSRunloop *runloop;
  4. }
  5. - (void)main:(id)sender;
  6. @end

  7. @implementation MyNSThread
  8. - (void)main:(id)sender
  9. {
  10.     NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
  11.     while (TRUE) {
  12.         //do some jobs
  13.         //break in some condition
  14.         [runloop executeOnce];
  15.         usleep(10000);
  16.         [pool drain];
  17.     }
  18.     [pool release];
  19. }
  20. @end
 
 
走到这里,我们就算是基本把Runloop结构抽象出来了。例如我有一个MyNSThread实例,myThread1。我可以给这个实例的线程添加需要的任务,而myThread1内部的MyNSRunloop对象会管理好这些任务。 
 
复制代码
  1. MyNSTimer *timer1=[MyNSTimer scheduledTimerWithTimeInterval:1
  2.                                                    target:obj1
  3.                                                  selector:@selector(download1:)];
  4. [myThread1.runloop addTimer:timer1];

  5. MyNSTimer *timer2=[MyNSTimer scheduledTimerWithTimeInterval:2
  6.                                                    target:obj2
  7.                                                  selector:@selector(download2:)];
  8. [myThread1.runloop addTimer:timer2];
 
 

当你看懂了上面的代码也许会感叹,‘原来是这么回事啊!为什么把这么简单的功能搞这么复杂呢?’ 其实就是这么回事,把Runloop抽象出来可以使得线程任务管理更加loose coupling,给设计模式提供更大的空间。这样第三方开发者不需要过深入的涉及线程内部代码而轻松管理线程任务。另外请注意,这里MyNSRunloop, MyNSTimer等类是我写得一个模拟情况,真实的NSRunloop实现肯定不是这么简单。这里只为了说明一个思想。这种思想贯穿整个cocoa framework从界面更新到event管理。



利用NSRunLoop阻塞NSOperation线程
使用NSOperationQueue简化多线程开发中介绍了多线程的开发,我这里主要介绍一下使用NSRunLoop阻塞线程。
主要使用在NStimer定时启用的任务或者异步获取数据的情况如socket获取网络数据,要阻塞线程,直到获取数据之后在释放线程。
下面是线程中没有使用NSRunLoop阻塞线程的代码和执行效果:
线程类:

#import <Foundation/Foundation.h> 
@interface MyTask : NSOperation {     

@end
#import "MyTask.h" 
@implementation MyTask 
-(void)main     
{      
    NSLog(@"开始线程=%@",self);      
    [NSTimer timerWithTimeInterval:2 target:self selector:@selector(hiandeTime:) userInfo:nil repeats:NO];      
}      
-(void)hiandeTime:(id)sender      
{      
    NSLog(@"执行了NSTimer");      
}      
-(void)dealloc      
{      
    NSLog(@"delloc mytask=%@",self);      
    [super dealloc];      

@end
线程添加到队列中:


- (void)viewDidLoad     
{      
    [super viewDidLoad];      
    NSOperationQueue *queue=[[NSOperationQueue alloc] init];      
    MyTask *myTask=[[[MyTask alloc] init] autorelease];      
    [queue addOperation:myTask];      
    MyTask *myTask1=[[[MyTask alloc] init] autorelease];      
    [queue addOperation:myTask1];      
    MyTask *myTask2=[[[MyTask alloc] init] autorelease];      
    [queue addOperation:myTask2];      
    [queue release];      
}
执行结果是:
2011-07-25 09:44:45.393 OperationDemo[20676:1803] 开始线程=<MyTask: 0x4b4dea0>   
2011-07-25 09:44:45.393 OperationDemo[20676:5d03] 开始线程=<MyTask: 0x4b50db0>    
2011-07-25 09:44:45.396 OperationDemo[20676:1803] 开始线程=<MyTask: 0x4b51070>    
2011-07-25 09:44:45.404 OperationDemo[20676:6303] delloc mytask=<MyTask: 0x4b4dea0>    
2011-07-25 09:44:45.404 OperationDemo[20676:5d03] delloc mytask=<MyTask: 0x4b50db0>    
2011-07-25 09:44:45.405 OperationDemo[20676:6303] delloc mytask=<MyTask: 0x4b51070>
可以看到,根本没有执行NSTimer中的方法,线程就释放掉了,我们要执行
NSTimer中的方法,就要利用NSRunLoop阻塞线程。下面是修改后的代码:

-(void)main     
{      
    NSLog(@"开始线程=%@",self);      
    NSTimer *timer=[NSTimer timerWithTimeInterval:2 target:self selector:@selector(hiandeTime) userInfo:nil repeats:NO];      
    [timer fire];      
    while (!didDisconnect) {      
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];      
    }      
}
执行结果如下:
2011-07-25 10:07:00.543 OperationDemo[21270:1803] 开始线程=<MyTask: 0x4d16380>     
2011-07-25 10:07:00.543 OperationDemo[21270:5d03] 开始线程=<MyTask: 0x4d17790>      
2011-07-25 10:07:00.550 OperationDemo[21270:6303] 开始线程=<MyTask: 0x4d17a50>      
2011-07-25 10:07:00.550 OperationDemo[21270:1803] 执行了NSTimer      
2011-07-25 10:07:00.551 OperationDemo[21270:5d03] 执行了NSTimer      
2011-07-25 10:07:00.552 OperationDemo[21270:6303] 执行了NSTimer      
2011-07-25 10:07:00.556 OperationDemo[21270:6503] delloc mytask=<MyTask: 0x4d16380>      
2011-07-25 10:07:00.557 OperationDemo[21270:6303] delloc mytask=<MyTask: 0x4d17790>      
2011-07-25 10:07:00.557 OperationDemo[21270:5d03] delloc mytask=<MyTask: 0x4d17a50>
我们可以使用NSRunLoop进行线程阻塞。
 

 

使用runloop阻塞线程的正确写法

Runloop可以阻塞线程,等待其他线程执行后再执行。

比如:

@implementation ViewController{
    BOOL end;
}

– (void)viewDidLoad
{
    [super viewDidLoad]; 
    NSLog(@”start new thread …”);
    [NSThread detachNewThreadSelector:@selector(runOnNewThread) toTarget:self withObject:nil];    
    while (!end) {
        NSLog(@”runloop…”);
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        NSLog(@”runloop end.”);
    }
    NSLog(@”ok.”);
}
-(void)runOnNewThread{
     NSLog(@”run for new thread …”);
    sleep(1);
    end=YES;
    NSLog(@”end.”);
}

但是这样做,运行时会发现,while循环后执行的语句会在很长时间后才被执行。

那是不是可以这样:

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];

缩短runloop的休眠时间,看起来解决了上面出现的问题。

不过这样也又问题,runloop对象被经常性的唤醒,这违背了runloop的设计初衷。runloop的作用就是要减少cpu做无谓的空转,cpu可在空闲的时候休眠,以节约电量。

那么怎么做呢?正确的写法是:

-(void)runOnNewThread{

     NSLog(@”run for new thread …”);
    sleep(1);
    [self performSelectorOnMainThread:@selector(setEnd) withObject:nil waitUntilDone:NO];
    NSLog(@”end.”);
}
-(void)setEnd{
    end=YES;
}

见黑体斜体字部分,要将直接设置变量,改为向主线程发送消息,执行方法。问题得到解决。

这里要说一下,造成while循环后语句延缓执行的原因是,runloop未被唤醒。因为,改变变量的值,runloop对象根本不知道。延缓的时长总是不定的,这是因为,有其他事件在某个时点唤醒了主线程,这才结束了while循环。那么,向主线程发送消息,将唤醒runloop,因此问题就解决了。


接下来我们看看具体的例子,包括如何实现线程执行的关联同步(join),以及UI线程run loop的一般使用技巧等。

假设有个线程A,它会启动线程B,然后等待B线程的结束。NSThread是没有join的方法,用run loop方式实现就比较精巧。

NSThread *A; //global

A = [[NSThread alloc] initWithTarget:self selector:@selector(runA) object:nil]; //生成线程A

[A start]; //启动线程A

- (void)runA

{

  [NSThread detachNewThreadSelector:@selector(runB) toTarget:self withObject:nil]; //生成线程B

  while (1)

  {

    if ([[NSRunLoop currentRunLooprunMode:@"CustomRunLoopMode" beforeDate:[NSDate distantFuture]]) //相当于join

    {

      NSLog(@"线程B结束");

      break;

    }

  }

}

- (void)runB

{

  sleep(1);

  [self performSelector:@selector(setData) onThread:A withObject:nil waitUntilDone:YES modes:@[@"CustomRunLoopMode"]];

}

实际运行时,过1秒后线程A也会自动结束。这里用到自定义的mode,一般在UI线程上调用run loop会使用缺省的mode。结合while循环,UI线程就可以实现子线程的同步运行(具体例子这里不再描述,可参看:http://www.cnblogs.com/tangbinblog/archive/2012/12/07/2807088.html)。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值