• iOS run loop NSRunloop


    Phone应用开发中关于NSRunLoop的概述是本文要介绍的内容,NSRunLoop是一种更加高明的消息处理模式,他就高明在对消息处理过程进行了更好的抽象和封装,这样才能是的你不用处理一些很琐碎很低层次的具体消息的处理,在NSRunLoop中每一个消息就被打包在input source或者是timer source中了,来看详细内容。

    1.什么是NSRunLoop

    我们会经常看到这样的代码:

    1. - (IBAction)start:(id)sender  
       
    2. {  
       
    3. pageStillLoading = YES;  
       
    4. [NSThread detachNewThreadSelector:@selector(loadPageInBackground:)toTarget:self withObject:nil];  
       
    5. [progress setHidden:NO];  
       
    6. while (pageStillLoading) {  
       
    7. [NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];  
       
    8. }  
       
    9. [progress setHidden:YES];  
       
    10. }  
    复制代码

    这段代码很神奇的,因为他会“暂停”代码运行,而且程序运行不会因为这里有一个while循环而受到影响。在[progress setHidden:NO]执行之后,整个函数想暂停了一样停在循环里面,等loadPageInBackground里面的操作都完成了以后才让[progress setHidden:YES]运行。这样做就显得简介,而且逻辑很清晰。如果你不这样做,你就需要在loadPageInBackground里面表示load完成的地方调用[progress setHidden:YES],显得代码不紧凑而且容易出错。
    [iGoogle有话说:应用程序框架主线程已经封装了对NSRunLoop runMode:beforeDate:的调用;它和while循环构成了一个消息泵,不断获取和处理消息;可能大家会比较奇怪,既然主线程中已经封装好了对NSRunLoop的调用,为什么这里还可以再次调用,这个就是它与Windows消息循环的区别,它可以嵌套调用.当再次调用while+NSRunLoop时候程序并没有停止执行,它还在不停提取消息/处理消息.这一点与Symbian中Active Scheduler的嵌套调用达到同步作用原理是一样的.]

    那么具体什么是NSRunLoop呢?其实NSRunLoop的本质是一个消息机制的处理模式。如果你对vc++编程有一定了解,在windows中,有一系列很重要的函数SendMessage,PostMessage,GetMessage,这些都是有关消息传递处理的API。

    但是在你进入到Cocoa的编程世界里面,我不知道你是不是走的太快太匆忙而忽视了这个很重要的问题,Cocoa里面就没有提及到任何关于消息处理的API,开发者从来也没有自己去关心过消息的传递过程,好像一切都是那么自然,像大自然一样自然?在Cocoa里面你再也不用去自己定义WM_COMMAD_XXX这样的宏来标识某个消息,也不用在switch-case里面去对特定的消息做特别的处理。难道是Cocoa里面就没有了消息机制?答案是否定的,只是Apple在设计消息处理的时候采用了一个更加高明的模式,那就是RunLoop。

    2. NSRunLoop工作原理

    接下来看一下NSRunLoop具体的工作原理,首先是官方文档提供的说法,看图:

    1.jpg

    通过所有的“消息”都被添加到了NSRunLoop中去,而在这里这些消息并分为“input source”和“Timer source” 并在循环中检查是不是有事件需要发生,如果需要那么就调用相应的函数处理。为了更清晰的解释,我们来对比VC++和iOS消息处理过程。

    VC++中在一切初始化都完成之后程序就开始这样一个循环了(代码是从户sir mfc程序设计课程的slides中截取):

    1. int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR  lpCmdLine,int nCmdShow){  
       
    2. ...  
       
    3. while (GetMessage(&msg, NULL, 0, 0)){  
       
    4. if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){  
       
    5. TranslateMessage(&msg);  
       
    6. DispatchMessage(&msg);  
       
    7. }  
       
    8. }  
       
    9. }  

    可以看到在GetMessage之后就去分发处理消息了,而iOS中main函数中只是调用了UIApplicationMain,那么我们可以介意猜出UIApplicationMain在初始化完成之后就会进入这样一个情形:

    1. int UIApplicationMain(...){  
       
    2. ...  
       
    3. while(running){  
       
    4. [NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];  
       
    5. }  
       
    6. ...  
       
    7. }

    所以在UIApplicationMain中也是同样在不断处理runloop才是的程序没有退出。刚才的我说了NSRunLoop是一种更加高明的消息处理模式,他就高明在对消息处理过程进行了更好的抽象和封装,这样才能是的你不用处理一些很琐碎很低层次的具体消息的处理,在NSRunLoop中每一个消息就被打包在input source或者是timer source中了,当需要处理的时候就直接调用其中包含的相应对象的处理函数了。

    所以对外部的开发人员来讲,你感受到的就是,把source/timer加入到runloop中,然后在适当的时候类似于[receiver action]这样的事情发生了。甚至很多时候,你都没有感受到整个过程前半部分,你只是感觉到了你的某个对象的某个函数调用了。

    比如在UIView被触摸时会用touchesBegan/touchesMoved等等函数被调用,也许你会想,“该死的,我都不知道在那里被告知有触摸消息,这些处理函数就被调用了!?”所以,消息是有的,只是runloop已经帮你做了!为了证明我的观点,我截取了一张debug touchesBegan的call stack,如图:
     

    2.jpg

    利用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,因此问题就解决了。

     
    NSRunLoop  runMode:
    NSDefaultRunLoopMode/NSRunLoopCommonModes
     
    eg.
    [[NSRunLoopcurrentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 
      [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
  • 相关阅读:
    php提示undefined index的几种解决方法
    划分树(poj2104)
    ACM-ICPC 2018 南京赛区网络预赛B
    AC Challenge(状压dp)
    UVALive5966(bfs)
    UVALive
    STL next_permutation 算法原理和实现
    凸包算法
    poj1873(枚举+凸包)
    CodeForces
  • 原文地址:https://www.cnblogs.com/linyawen/p/2864879.html
Copyright © 2020-2023  润新知