iOS RunLoop都做了什么?

·  阅读 406

RunLoop的运行流程图

upload-images.jianshu.io/upload_imag…

CFRunLoop底层实现流程图.png

RunLoop从入口函数开始都做了什么?

1.从CFRunLoopRun入口函数可以看到,它用do...while不断执行CFRunLoopRunSpecific 2.CFRunLoopRunSpecific中传入当前的Loop、默认Modelsecond值1.0e10、BOOL returnAfterSourceHandled

CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
复制代码

3.根据传入的modeName,通过__CFRunLoopFindMode获得当前的CFRunLoopModeRef currentMode。 4. 如果没找到或者mode中没有注册任何事件,则就此停止,不进入循环

    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        Boolean did = false;
        if (currentMode) __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopUnlock(rl);
        return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
复制代码
  1. 取上一次运行的mode,将当前runloopmode替换成 第三步找到的mode
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
复制代码

6.初始化一个resultkCFRunLoopRunFinished,先通知Observers: RunLoop 即将进入 loop,然后执行__CFRunLoopRun,结果为result赋值,最后在通知 Observers: RunLoop 即将退出。

    int32_t result = kCFRunLoopRunFinished;
    if (currentMode->_observerMask & kCFRunLoopEntry ){
        /// 1. 通知 Observers: RunLoop 即将进入 loop。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    }
    result = __CFRunLoopRun(rl, currentMode, seconds, 
returnAfterSourceHandled, previousMode);
    if (currentMode->_observerMask & kCFRunLoopExit ){
        /// 10. 通知 Observers: RunLoop 即将退出。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
     }
复制代码

7.当RunLoop 退出后,进行一系列操作,Unlock当前通过第三步获取到的mode,并将当前RunLoop_currentMode设置为先前保存下来的previousMode,返回result

    __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
复制代码

核心方法__CFRunLoopRun里做了什么?

image.png

1.通知observer:即将进入RunLoop

if (currentMode->_observerMask & kCFRunLoopEntry ){
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
}
复制代码

__CFRunLoopRun内执行的就是上图虚线框内的内容 以下内容,按图中顺序描述。 2.通知observer:将要处理Timer

if (rlm->_observerMask & kCFRunLoopBeforeTimers) {
    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
}
复制代码

3.通知observer:将要处理source0

if (rlm->_observerMask & kCFRunLoopBeforeSources) {
    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
}
复制代码

4.处理source0

    Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
复制代码

5.如果有source1,跳转到第九步,source1包含一个 mach_port和一个回调(函数指针),

    msg = (mach_msg_header_t *)msg_buffer;
    if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
         //如果接收到了消息的话,前往第9步开始处理msg
         goto handle_msg;
     }
复制代码

6.通知observerrunloop即将休眠。判断如果没有sources0事件处理并且没有超时,pollfalse;如果有sources0事件处理 或者 超时,poll都为true。如果pollfalserlm->_observerMaskkCFRunLoopBeforeWaiting,就执行__CFRunLoopDoObservers

if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
复制代码

7.设置runloop为休眠状态__CFRunLoopSetSleeping(rl);后执行一个内循环do...while,进入循环后线程进入休眠,直到收到新消息才跳出该循环(被唤醒),继续执行run loop。

这里有个纠结的点:为什么设置的是runloop为休眠状态,休眠的却是线程?


runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。 runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。

上面这段摘取自这位大佬


在等待唤醒的过程中,如果USE_DISPATCH_SOURCE_FOR_TIMERS将会执行一个内循环

do {
            //从缓冲区读取消息
            msg = (mach_msg_header_t *)msg_buffer;
            //7.调用__CFRunLoopServiceMachPort, 监听waitSet所指定的mach port端口集合, 如果没有port message,进入 mach_msg_trap, RunLoop休眠,直到收到port message或者超时
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
            //收到消息之后,livePort的值为msg->msgh_local_port,然后livePort!=modeQueuePort,所以会走else
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
                //耗尽内部队列,如果有一个回调的block 设置了timerFired,跳出循环,并且为这个timer服务(翻译过来是这样,但是不太懂)
                while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                if (rlm->_timerFired) {
                    // Leave livePort as the queue port, and service timers below
                    rlm->_timerFired = false;
                    break;//退出循环,即将去执行唤醒操作
                    
                } else {
                    //如果msg不为空,并且msg跟msg_buffer不匹配,就释放这条msg,当做什么事都没发生
                    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                }
            } else {
                // Go ahead and leave the inner loop.
                //  继续往下走,离开内循环
                break;//退出循环,即将去执行唤醒操作
            }
        } while (1);
复制代码

这里有一点让我很纠结,就是这些各种各样的Port都是啥,livePort如何跟modeQueuePort相等。

 if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) 
复制代码

这个判断又要如何成立。 于是我全局搜索 modeQueuePort,发现有赋值的地方只有一个且需要USE_DISPATCH_SOURCE_FOR_TIMERS

modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
复制代码

然后继续搜索livePort,在__CFRunLoopServiceMachPort的实现方法中找到了,当内核成功收到消息时,livePort 会被赋值为msg->msgh_local_port

if (MACH_MSG_SUCCESS == ret) {//成功收到消息
    *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
    return true;
}
复制代码

所以,只要成功收到消息,livePort就不等于modeQueuePort,那么就会跳出循环。

但是继续往下走又会看到一串代码,判断规则跟上面一模一样,但是这个方法里却唤醒了RunLoop

#if USE_DISPATCH_SOURCE_FOR_TIMERS
       if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
复制代码

我勒个去,什么玩意,上面好不容易解读出来,到这里就冲突了,一度怀疑人生。吓得我赶紧找资料去。 终于,我在这位大佬的博客里找到了关于这段的解释。


这里不是从runLoop休眠后唤醒到这里的,而是在runLoop10步中的第五步跳转过来的,是处理计时器事件


这下子总算是把内循环理得差不多清楚了

8.通知observer:线程刚被唤醒。 休眠结束。

if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) {
    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
}
复制代码

9:处理唤醒时收到的消息,然后回到第二步。

handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
         if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            // do nothing on Mac OS
        }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            //这里不是从runLoop休眠后唤醒到这里的
            //而是在runLoop10步中的第五步跳转过来的,是处理计时器事件      
            //www.jianshu.com/p/ec629063390f
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
        /// 9.2 如果有dispatch到main_queue的block,执行block
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);

            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            // Despite the name, this works for windows handles as well
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                mach_msg_header_t *reply = NULL;
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                if (NULL != reply) {
                    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
                }
            }
        }

        /// 执行跟上面一个样的方法,应该就是回到第二步
        __CFRunLoopDoBlocks(rl, rlm);
复制代码

10.通知observer:即将退出RunLoop。为retVal赋值,如果retVal != 0,将会退出循环,并返回retVal

        if (sourceHandledThisLoop && stopAfterHandle) {
            /// 进入loop时参数说处理完事件就返回。
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            /// 超出传入参数标记的超时时间了
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            /// 被外部调用者强制停止了
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            /// 自动停止了
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            /// source/timer/observer一个都没有了
            retVal = kCFRunLoopRunFinished;
        }
        /// 如果没超时,mode里不为空,loop也没被停止,那继续loop。
复制代码

回到外层方法__CFRunLoopRun调用并且为result赋值。

     if (currentMode->_observerMask & kCFRunLoopExit ){
        /// 10. 通知 Observers: RunLoop 即将退出。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
     }
     return result;
复制代码

result == kCFRunLoopRunStopped && result == kCFRunLoopRunFinished 结束CFRunLoopRun

完整带注释的RunLoop源码

分类:
阅读
标签: