NSRunLoop概述和原理

21 Comments

1.什么是NSRunLoop?
我们会经常看到这样的代码:

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

这段代码很神奇的,因为他会“暂停”代码运行,而且程序运行不会因为这里有一个while循环而受到影响。在[progress setHidden:NO]执行之后,整个函数想暂停了一样停在循环里面,等loadPageInBackground里面的操作都完成了以后才让[progress setHidden:YES]运行。这样做就显得简介,而且逻辑很清晰。如果你不这样做,你就需要在loadPageInBackground里面表示load完成的地方调用[progress setHidden:YES],显得代码不紧凑而且容易出错。
那么具体什么是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
2
3
4
5
6
7
8
9
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR  lpCmdLine,int nCmdShow){
...
while (GetMessage(&msg, NULL, 0, 0)){
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}

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

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

所以在UIApplicationMain中也是同样在不断处理runloop才是的程序没有退出。刚才的我说了NSRunLoop是一种更加高明的消息处理模式,他就高明在对消息处理过程进行了更好的抽象和封装,这样才能是的你不用处理一些很琐碎很低层次的具体消息的处理,在NSRunLoop中每一个消息就被打包在input source或者是timer source中了,当需要处理的时候就直接调用其中包含的相应对象的处理函数了。所以对外部的开发人员来讲,你感受到的就是,把source/timer加入到runloop中,然后在适当的时候类似于[receiver action]这样的事情发生了。甚至很多时候,你都没有感受到整个过程前半部分,你只是感觉到了你的某个对象的某个函数调用了。比如在UIView被触摸时会用touchesBegan/touchesMoved等等函数被调用,也许你会想,“该死的,我都不知道在那里被告知有触摸消息,这些处理函数就被调用了!?”所以,消息是有的,只是runloop已经帮你做了!为了证明我的观点,我截取了一张debug touchesBegan的call stack,有图有真相:

现在会过头来看看刚才的那个会“暂停”代码的例子,有没有更加深入的认识了呢?

21 Comments (+add yours?)

  1. bandw
    一 06, 2011 @ 14:24:42

    错别字很多啊

    回复

    • vagase
      一 28, 2011 @ 11:45:31

      输入法。。。

  2. macLinuxP
    一 06, 2011 @ 16:55:51

    能不能在讲详细点阿,网上真的很难找这方面的

    回复

    • vagase
      一 28, 2011 @ 11:45:52

      您想要怎样个详细法?

  3. Laoyur
    七 04, 2011 @ 16:48:49

    这段代码很神奇的,因为他会“暂停”代码运行,而且程序运行不会因为这里有一个while循环而受到影响。
    ————————————————
    楼主哥,你这段代码阻塞了UI(主线程)的吧。

    回复

    • Near
      七 08, 2011 @ 18:36:06

      不会阻塞UI,但是UITrackingRunLoopMode的事件是没有办法执行的 :(

    • 文祥
      十 28, 2011 @ 15:01:43

      如果这个方法由一个按钮来触发,按钮会停留在变蓝的状态,因为这个函数一直没有执行完,也可以说阻塞了按钮的UI,但是界面上其他部分还是可以点击的。我看的也比较糊涂

  4. duanxiaobing
    七 21, 2011 @ 13:37:26

    大哥你好强啊,往后多指点指点小弟哈。。。。

    回复

  5. duanxiaobing
    七 21, 2011 @ 13:46:41

    大哥,从人人网上加你,叫你这个名字的四个人呢,到底哪个是你啊?
    验证码是什么啊??

    回复

    • Near
      八 12, 2011 @ 10:18:25

      加我新浪微博就好了~

  6. yaoohfox
    七 27, 2011 @ 13:08:48

    写的很好!顶一个!

    回复

  7. npp
    八 20, 2011 @ 10:47:06

    这些时间在研究多线程结果看到CFRunLoop不太理解,也查了些资料,可能是我理解能力有限仍然是半知半解,不过看了你的这些说明,感觉豁然开朗,虽然仍然只是知道皮毛但我想至少找到了入口,在此谢了..希望能从你这儿学到更多哈,不过没学费交哦 嘿嘿…也就顶一个了..!!

    回复

  8. Jack
    八 23, 2011 @ 14:07:22

    你好,有些地方我还是有点疑惑。
    加入新发起一个线程,在这个线程发起网络请求。
    那这个网络请求会不会直接加入这个runloop上,
    还有runloopMode工作原理又是怎么样的

    回复

    • Near
      九 09, 2011 @ 00:33:00

      首先这个线程必须有用while(YES){runloop…}这样“配置”好该线程的runloop,不然网络请求的callback是拿不到的。runloopMode原理很简单,就是对runloop资源分类,在某个时刻你可以制定runloop的mode,只run你感兴趣的一些时间,比如当手触摸这屏幕滑动的时候,这个时候mainthread的runloopmode为UIEventTrackingRunLoopMode, 这个时候程序就只能响应mode为evetntracking的事件了,屏蔽了那些不感兴趣的事件,保证了用户体验。

  9. Vicky
    九 05, 2011 @ 12:20:50

    cocoa男程序员你好:]看到官方关于run loop长长的文档,就想上网搜搜中文看看嘿。。结果打开的第一篇文章居然是你的~哈哈。。亲切。。

    回复

    • Near
      十一 11, 2011 @ 12:46:46

      谢谢捧场~

  10. Magic
    十一 11, 2011 @ 13:03:06

    最近在项目中正好在用run loop,不用开新的thread就能达到异步效果,非常棒。不过run loop代码的可读性比较差,要达到lz示例代码的效果个人觉得用GCD会更优雅。

    回复

  11. jfwf-2011
    一 16, 2012 @ 14:35:41

    学习了,多谢!

    回复

  12. 斌斌
    四 10, 2012 @ 18:27:21

    兄弟 你好!知道怎么处理socket 批量上传吗?我这边用操作队列 ,最大数设置为2时就会出错

    回复

Leave a Reply

使用新浪微博登录
  • RSS
  • Twitter
  • Buzz
  • LinkedIn
  • Flickr