iPhone应用开发中关于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具体的工作原理,首先是官方文档提供的说法,看图:

通过所有的“消息”都被添加到了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,如图:

利用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]];
 
 
 
 

from CFRunLoop Reference

CFRunLoop monitors sources of input to a task and dispatches control when they become ready for processing.

Examples of input source might include user input devices,network connection,periodic or timed-delay events,and asynchronous callbacks.

Three types of objects can be monitored by a run loop:

sources(CFRunLoopSource)

timers(CFRunLoopTimer)

observers(CFRunLoopObserver)

Each sources,times or observers added to a run loop must be associated with one or more run loop modes.

There is exactly one run loop per thread.

RunLoop 是事件循环,用于schedule work 和协调输入事件.

RunLoop 接受2种不同的事件源,input sources (异步事件)和 timer sources(同步事件)

何时使用RunLoop?

1.使用ports或自定义输入源 与其他线程通讯

2.在线程中使用定时器

3.在程序中使用performSelector等方法

4.让线程周期性执行某个任务

NSRunLoop的进一步理解的更多相关文章

  1. iOS开发之NSRunLoop的进一步理解

    http://www.cnblogs.com/pengyingh/articles/2343920.html iPhone应用开发中关于NSRunLoop的概述是本文要介绍的内容,NSRunLoop是 ...

  2. Java内存管理的进一步理解-模拟过程图解

    Java内存管理的进一步理解-模拟过程图解--转载 java的内存管理分为: 1.堆内存:2.栈内存:3.方法区:4.本地方法区 /* 1:方法区      方法区存放装载的类数据信息包括:      ...

  3. 对于python装饰器结合递归的进一步理解

    对于python装饰器结合递归的进一步理解 代码如下: import functools def memoize(fn): print('start memoize') known = dict() ...

  4. iOS NSRunloop的简单理解

    最近学习了下NSRunloop. 作一下简单的理解: 1.runloop与线程的关系,每一个线程创建是都会有伴有一个runloop诞生,runloop用来接收事件源,让线程执行事件.当没有事件处理时, ...

  5. 对iOS后台模式最多10分钟运行时间的进一步理解

    在app进入后台时,系统初始默认是只有10s的处理时间,但如果10s不够,我们可以主动申请,网上流传最多的一个说法是10分钟. 但这种说法有个前提: 那就是iOS7之前,是这样 但从iOS7开始,我们 ...

  6. AutoResetEvent waitone set进一步理解补充

    AutoResetEvent 的定义 //定义两个信号锁 AutoResetEvent ReadTxt = new AutoResetEvent(false); AutoResetEvent Uplo ...

  7. 对Java中字符串的进一步理解

    字符串在程序开发中无处不在,也是用户交互所涉及到最频繁的数据类型,那么字符串不仅仅就是我们简单的理解的String str = "abc";一起来更加深入的看一下 在Java中,字 ...

  8. 通过回调函数的理解来进一步理解ajax及其注意的用法

    一,再一次理解回调函数 (function($){ $.fn.shadow = function(opts){ //定义的默认的参数 var defaults = { copies: 5, opaci ...

  9. 『cs231n』卷积神经网络的可视化与进一步理解

    cs231n的第18课理解起来很吃力,听后又查了一些资料才算是勉强弄懂,所以这里贴一篇博文(根据自己理解有所修改)和原论文的翻译加深加深理解,其中原论文翻译比博文更容易理解,但是太长,而博文是业者而非 ...

随机推荐

  1. 2016年最新mac下vscode配置golang开发环境支持debug

    网上目前还找不到完整的mac下golang环境配置支持,本人配置成功,现在整理分享出来. mac最好装下xcode,好像有依赖关系安装Homebrew打开终端窗口, 粘贴脚本执行/usr/bin/ru ...

  2. 【开源】C#跨平台物联网通讯框架ServerSuperIO(SSIO)

    [连载]<C#通讯(串口和网络)框架的设计与实现>-1.通讯框架介绍 [连载]<C#通讯(串口和网络)框架的设计与实现>-2.框架的总体设计 目       录 C#跨平台物联 ...

  3. LinQ to SQL用法详解

    LinQ是指集成化查询语言,通过映射将数据库内的表名变为C#的类名,将列名作为属性名,将表的关系作为类的成员对象.O--M--R O-Object对象(李昌辉)R-Relation关系M-Mappin ...

  4. CString转换为LPSTR和LPSTR转化为CString

    一.CString转换为LPSTR 方法一: CString strFileName LPSTR lpstr - strFileName.GetBuffer(); strFileName.Releas ...

  5. iframe高度自适应(同域)

    今天解决了iframe高度自适应的问题,不过这只是同域下的页面嵌入,以下是代码: function SetCwinHeight(){ var iframeid = document.getElemen ...

  6. 《Web开发中块级元素与行内元素的区分》

    一.块级元素的特性: 占据一整行,总是重起一行并且后面的元素也必须另起一行显示. HTML中块级元素列举如下: address(联系方式信息) article(文章内容) aside(伴随内容) au ...

  7. 错误:当你使用id作为sharepoint的自定义页面的查询参数时,总会提示项目不存在!

    No item exists at http://SERVER/SITE/mypage.aspx?ID=1. It may have been deleted or renamed by anothe ...

  8. 网络安全——数据的加密与签名,RSA介绍

    一. 密码概述 发送者对明文进行加密然后生成密文,接受者再对密文解密得到明文的过程. 现在使用的所有加密算法都是公开的!但是密钥肯定不是公开的. 1 散列(哈希)函数 通常有MD5.SHA1.SHA2 ...

  9. x01.TodoList:Asp.Net 5 初探

    ASP.NET 5 是比较新的,除了汤姆的博文,学习资料并不多.而学习没有例子上手,是比较痛苦的. 1.运行 vs2015,新建项目,选择 Asp.Net 5 WebApp 模板,默认运行即可.对照汤 ...

  10. 如何利用报表工具FineReport实现报表列的动态展示

    相信动态列的实现困扰了很多人,大数据量,多字段的加载将会非常耗时,数据又做不到真正的动态灵活.现有的方式都是通过变向的隐藏等方式来实现. 那该如何解决呢?这里分享帆软报表设计器FineReport的实 ...