十二、RunLoop详解

一、什么是RunLoop

先来看下RunLoopCFRunLoopRun函数的实现:

void CFRunLoopRun(void){
  int32_t result;
  do{
    result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
  } while(kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result)
}

开启RunLoop之后会调用CFRunLoopRun函数,该函数使用do{}while()循环不断处理CFRunLoopRunSpecific,直到RunLoop结束或者需要停止才退出。
RunLoop接收到消息时(用户输入、程序执行)会处理消息,空闲时休眠,是一种Event loop的模型。RunLoop开启时,一直处于接受消息-等待-处理的循环中。这里我们着重介绍下CFRunLoop,至于NSRunLoop,其实就是对CFRunLoop的一层封装。

二、RunLoopMode

RunLoopMode就是RunLoop的相关模式,其定义如下:

struct __CFRunLoopMode{
  CFRuntimeBase _base;
  pthread_mutex_t _lock; // 锁
  CFStringRef _name; // 模式名字
  Boolean _stopped; // 是否停止
  char _padding[3];
  CFMutableSetRef _sources0; // source0
  CFMutableSetRef _sources1;  // source1
  CFMutableArrayRef _observers; // observers
  CFMutableArrayRef _timers; // timers
  
  // ... other
};

常用的Mode有以下几个:

Mode 说明
kCFRunLoopDefaultMode 默认运行方式
UITrackingRunLoopMode 使用这个Mode去跟踪来自用户交互的事件
GSEventReceiveRunLoopMode 用来接受系统事件,内部的Run Loop Mode
kCFRunLoopCommomModes 这是一个伪模式,其为一组run loop mode的集合
NSConnectionReplyMode 使用这个Mode去监听NSConnection对象的状态,我们很少需要自己使用这个Mode

三、RunLoopSource

struct __CFRunLoopSource{
  CFRuntimeBase _base;
  uint32_t bits;
  pthread_mutex_t _lock;
  CFIndex _order;
  CFMutableBagRef _runLoops;
  union{
    CFRunLoopSourceContext version0;
    CFRunLoopSourceContext1 version1;
  } _context;
};

source有两个版本:version0version1,分别对应CFRunLoopModesource0source1
source0需要进行标记为可操作,然后再唤醒(wakeup)runloop进行处理。
source1是基于mach_port的,用于跟其它线程互相发送消息,通信。

四、RunLoopTimer

CFRunLoopTimer是定时器,包含时间和回调函数指针,时间点到时,RunLoop会被唤醒并执行该定时器。

五、RunLoopObserver

struct __CFRunLoopObserver{
  CFRuntimeBase _base;
  pthread_mutex_t _lock;
  CFRunLoopRef _runLoop;
  CFIndex _rlCount;
  CFOptionFlags _activities;
  CFIndex _order;
  CFRunLoopObserverCallBack _callout;
  CFRunLoopObserverContext _context;
};

CFRunLoopObserverRef是一个观察者,当RunLoop的状态改变时,会通过_callout回调。状态有以下几种:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity){
  kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
  kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
  kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
  kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
  kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
  kCFRunLoopExit = (1UL << 7), // 即将退出Loop
  kCFRunLoopAllActivities = 0x0FFFFFFFU
}

六、RunLoop工作流程

首先来看下简化版的CFRunLoopRunSpecific函数:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled){
  // 即将进入Loop
  __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
  result = __CFRunLoop(rl, currentMode, seconds, returnAfterSourceHandled, previousMode){
    // 即将处理 Timer
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopBeforeTimers);
    // 即将处理 Source
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopBeforeSources );

    // source0
    __CFRunLoopDoBlocks(rl, rlm);
    BOOLean sourceHandledThisStop = __CFRunLoopDoSource0(rl, rlm, stopAfterHandle);
    if(sourceHandledThisStop){
      //如果有source0需要处理的话,sourceHandledThisStop为true
      __CFRunLoopDoBlocks(rl, rlm);
    }

    if(MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime){
      // 检查livePort是否有待处理的消息,如果有则goto handle_msg,否则进入休眠等待唤醒
      msg = (mach_msg_header_t *)msg_buffer;
      if(__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)){
            goto handle_msg;
        }
    }
    
    // 即将进入休眠
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopBeforeWaiting);
    
    // 监听端口,等待消息唤醒
    do{
        msg = (mach_msg_header_t *)msg_buffer;
        // TIMEOUT_INFINITY无限等待
        __CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
        if(modeQueuePort != MACH_PROT_NULL && modeQueuePort == modeQueuePort){
          while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
          if(rlm->_timerFired){
            // 处理timer
            rlm->_timerFired = false;
            break;
          }else{
            // free msg
          }
        }else{
            // 处理其它消息
            break;
        }
    } while(1);

    // 刚从休眠中唤醒
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopAfterWaiting );
     
    handle_msg:
    // 根据livePort处理timer, source1等
    if(MACH_PORT_NULL == livePort){
    }else if(livePort == rl->_wakeUpPort){
      // wakeUp
    }else if(rlm->_timerPort == livePort){
      __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
    }else if(livePort == dispatchPort){
      __CFRUNLOOP_IS_SERVICINT_THE_MAIN_DISPATCH_QUEUE__(msg);
    }else{
       // source1
        __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
    }

    // 改变状态,如果retVal不为0,则代表需要结束RunLoop了
    if(sourceHandledThisLoop && stopAfterHandle){
        retVal = kCFRunLoopRunHandledSource;
    }else if(timeout_context->termTSR < mach_absolute_time()){
        retVal = kCFRunLoopRunTimedOut;
    }else if(_CFRunLoopIsStopped(rl)){
        retVal = kCFRunLoopRunStopped;
    }else if(rlm->_stopped){
        rlm->_stopped = false;
        retVal = kCFRunLoopRunStopped;
    }else if(_CFRunLoopModeIsEmpty(rl, rlm, previousMode)){
        retVal = kCFRunLoopRunFinished;
    }
  } while(0 == retVal)

  // 7. 即将退出Loop
  __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit );
  return result;
}

RunLoop流程主要如下:

  1. 即将进入Loop
  2. 将要处理Timer
  3. 将要处理Source0
  4. 处理Source0
  5. 如果有Source1或Timer待处理,则跳转到第步
  6. 通知即将休眠
  7. 进入休眠,等待唤醒
  8. 通知被唤醒
  9. 处理Source1和Timer
  10. 如果RunLoop仍需处理其它消息,则跳转到2
  11. 即将推出Loop
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容

  • Runloop 是和线程紧密相关的一个基础组件,是很多线程有关功能的幕后功臣。尽管在平常使用中几乎不太会直接用到,...
    jackyshan阅读 9,850评论 10 75
  • 不得不说,人的惰性是真可怕啊。从上周六就到写runLoop的建议开始,星期三告诉自己从星期四开始着手写这篇博客。然...
    老司机Wicky阅读 7,155评论 20 137
  • 此文转载自深入理解RunLoop,为了方便自己阅读,发在简书上RunLoop 是 iOS 和 OS X 开发中非常...
    渐行渐远ty阅读 391评论 0 0
  • 转载:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling阅读 1,436评论 0 13
  • 下面的代码输出多少?修改代码让 fnArri 输出 i。使用 两种以上的方法 var fnArr = [];for...
    Cart86阅读 157评论 0 0