美文网首页
十二、RunLoop详解

十二、RunLoop详解

作者: LNG61 | 来源:发表于2018-08-03 15:48 被阅读0次

    一、什么是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

    相关文章

      网友评论

          本文标题:十二、RunLoop详解

          本文链接:https://www.haomeiwen.com/subject/rfxkvftx.html