NSRunLoop的进一步理解

NSRunLoop的进一步理解

2011-8-22 15:42| 发布者: Vincent| 查看: 716| 评论: 17|原作者: iGoogle

摘要: 本文基于一篇网络文章,加入了一些自己的理解,希望对大家有所帮助 iPhone应用开发中关于NSRunLoop的概述是本文要介绍的内容,NSRunLoop是一种更加高明的消息处理模式,他就高明在对消息处理过程进行了更好的抽象和 ...
本文基于一篇网络文章,加入了一些自己的理解,希望对大家有所帮助

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的进一步理解_第1张图片 
通过所有的“消息”都被添加到了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,有图有真相,如图:
 
现在会过头来看看刚才的那个会“暂停”代码的例子,有没有更加深入的认识了呢?
收藏 分享
发表评论

最新评论

g3ee  2011-9-7 18:13
rongjianxing 发表于 2011-9-7 17:31 
”比如在UIView被触摸时会用touchesBegan/touchesMoved等等函数被调用,也许你会想,“该死的,我都不知道在 ...

顶牛人
rongjianxing  2011-9-7 17:31
”比如在UIView被触摸时会用touchesBegan/touchesMoved等等函数被调用,也许你会想,“该死的,我都不知道在那里被告知有触摸消息,这些处理函数就被调用了!“

在win中,如果使用纯api手工打造,的确没有这功能。
但使用mfc,wtl等类库,OnCreate()等函数也是一样”都不知道在那里被告知有消息,这些处理函数就被调用了“
rongjianxing  2011-9-7 17:28
",Cocoa里面就没有提及到任何关于消息处理的API,开发者从来也没有自己去关心过消息的传递过程,好像一切都是那么自然,像大自然一样自然?在Cocoa里面你再也不用去自己定义WM_COMMAD_XXX这样的宏来标识某个消息,也不用在switch-case里面去对特定的消息做特别的处理。"

这个说法也有问题,cocoa与win的开发者都要关心消息的处理,只是在windows下拉使用api时,要做WM_COMMAD_XXX和消息函数对应起来,而cocoa是实现特定接口的delegate与引起消息的源对应起来。
而通过使用ide,都可以通过拉拖点击来完成对应操作。
rongjianxing  2011-9-7 17:13
“可能大家会比较奇怪,既然主线程中已经封装好了对NSRunLoop的调用,为什么这里还可以再次调用,这个就是 它与Windows消息循环的区别,它可以嵌套调用.”
这句不准确,windows的消息循环也可以嵌套调用,比如win处理拖动选择时可以这样实现:
  1. case WM_LBUTTONDOWN:
  2. {
  3.         GetCapture();
  4.         while (GetMessage (&msg, NULL, 0, 0)) // -------开始选择------
  5.         {
  6.                 if (WM_MOUSEMOVE == msg.message)
  7.                 {
  8.                         // 画选择框
  9.                 }
  10.                 if (WM_LBUTTONUP == msg.message)
  11.                 {                        
  12.                         // 画选择框
  13.                         ReleaseCapture ();
  14.                         break; // 跳出循环 
  15.                 }
  16.         } // while 结束.  -------结束选择-------
  17. }
复制代码
bishop  2011-9-7 11:45
mark    学习了
pxfokko  2011-9-6 16:39
学习了 
ericni  2011-9-1 11:06
有点懂了,多谢
snakeninny  2011-8-26 10:56
支持一下!小弟还看不懂……
iGoogle  2011-8-26 10:12
happyboyxq 发表于 2011-8-26 10:01 
我还有点不明白,从这个方法来看,调用start函数的输入源(暂称为输入源1),此时输入源1应该是拥有运行循环 ...

他们是不同的线程,
新启动的线程(称为线程1)根本不在主线程(我们姑且称start函数执行的线程为主线程)中执行。
主线程和线程1的切换执行是cpu调度的事
这里的NSRunLoop是主线程中的消息循环机制,每个线程都有自己的NSRunLoop
happyboyxq  2011-8-26 10:01
我还有点不明白,从这个方法来看,调用start函数的输入源(暂称为输入源1),此时输入源1应该是拥有运行循环的控制权的(文档上说运行循环会将控制权交给使用者的)
- (IBAction)start:(id)sender  
{  
pageStillLoading = YES;  
此处又新建了一个线程,从代码会在下面的运行循环处停止可知,该线程为输入源2,那何时这个输入源2会拥有运行循环的控制权并执行函数呢?此时的输入源1并未执行完成,应该不会归还控制权给运行循环才对啊。这样不是陷入了一个循环吗,输入源1未交出控制权,输入源2在等待控制权,那此时程序是怎么执行到最后的呢,pageStillLoading设为NO应该是在输入源2里面做的。这表示中间应该有某个过程把输入源1的控制权交给了输入源2,我就是想请教一下这个过程是在哪,什么时候执行的。
[NSThread detachNewThreadSelector:@selector(loadPageInBackground:)toTarget:self withObject:nil];  
[progress setHidden:NO];  
while (pageStillLoading) {  
此处调用这个方法是否就是输入源1为了交出控制权呢?这样输入源2就有机会执行,待执行完成后退出,pageStillLoading=NO,因为输入源1仍存在于运行循环中,所以继续执行,这样后面的代码才得以执行呢?
[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];  
}  
[progress setHidden:YES];  
}  
cheungching  2011-8-25 20:45
为什么在UIApplicationDelegate中做这个会出现一会黑屏呢?!
cheungching  2011-8-25 20:04
呵呵,分析的太好了。
dymx101  2011-8-24 17:17
学习了
iGoogle  2011-8-24 11:57
  1. [NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];  
复制代码
相当于:
  1. TranslateMessage(&msg);  
  2. DispatchMessage(&msg);  
复制代码
所以即使嵌套使用,它仍旧在不停处理消息,不会让程序停止运行,界面更不会失去响应
iGoogle  2011-8-24 11:55
happyboyxq 发表于 2011-8-24 09:47 
while (pageStillLoading) {  
[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NS ...

不是一直等待,实际上它还在处理消息,界面不会停止响应,
它的作用是同步,只是不执行while循环后面跟着的语句.
happyboyxq  2011-8-24 09:47
while (pageStillLoading) {  
[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];  
}  
里的NSRunLoop应该是主线程的运行循环吧,这样主线程不是一直处于等待状态?如果我想做点别的什么怎么办呢?再启一个线程?
pp_psy  2011-8-22 20:08
顶,多谢iGoogle哥

查看全部评论(17)

你可能感兴趣的:(NSRunLoop的进一步理解)