CFRunLoop源码分析笔记

Posted WoodBear009

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CFRunLoop源码分析笔记相关的知识,希望对你有一定的参考价值。

  之前看过不少别人博客里对CFRunLoop源码的分析,但自己一直没有去看过,今天回顾相关知识时,决定自己去看看相关的源码实现,确实又有了一些新的认识与收获.

CFRunLoopRun、CFRunLoopRunInMode

首先是两个简单的入口函数

//两个函数分别是让runloop跑在kCFRunLoopDefaultMode下,与让runloop跑在指定mode下
//两个函数区别不大,最终都调用CFRunLoopRunSpecific
/*runloop同时只能跑在一个mode下,切换mode,要退出当前runloop
 什么叫退出当前runloop?所谓的退出,通过源码有了更直观的认识,其实就是传递不同的modeName,
 一遍一遍的跑CFRunLoopRunSpecific->CFRunLoopRun逻辑代码,每跑完一遍,就是所谓的退出,马上迎来下一次CFRunLoopRunInMode调用
*/
void CFRunLoopRun(void) 	/* DOES CALLOUT */
    int32_t result;
    do 
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
     while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)      /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);

CFRunLoopRunSpecific

/*
参数1:传入runloop对象,实际传入的都是CFRunLoopGetCurrent
参数2:传入当前要运行的mode名称
参数3:runloop的超时时间
参数4:主要__CFRunLoopRun会用到,后面说
*/
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)      /* DOES CALLOUT */
    //得到当前的mode对象
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    //看看是不是一个空mode,后面会简单分析__CFRunLoopModeIsEmpty实现
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode))
    
    //通知进入kCFRunLoopEntry状态,,后面会简单看看__CFRunLoopDoObservers实现
	if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    //进入关键函数,__CFRunLoopRun
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    //通知进入kCFRunLoopExit状态
	if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;

看看几个重要相关函数

__CFRunLoopModeIsEmpty

//就是看看这个mode里有没有source0、source1、timer,只要存在source0或source1或timer就不是空的.同时看看这个mode是不是属于当前的runloop
static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) 
    if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) return false;
    if (NULL != rlm->_sources1 && 0 < CFSetGetCount(rlm->_sources1)) return false;
    if (NULL != rlm->_timers && 0 < CFArrayGetCount(rlm->_timers)) return false;
    struct _block_item *item = rl->_blocks_head;
    while (item) 
        struct _block_item *curr = item;
        item = item->_next;
        Boolean doit = false;
        if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) 
            doit = CFEqual(curr->_mode, rlm->_name) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
         else 
            doit = CFSetContainsValue((CFSetRef)curr->_mode, rlm->_name) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
        
        if (doit) return false;
    
    return true;

__CFRunLoopDoObservers

//调用observer回调
//原来用过添加obserber监听runloop状态,在特定状态下执行相关逻辑,现在通过源码,看到了相关的具体实现
static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity) 	/* DOES CALLOUT */
    //遍历rlm->_observers,放到collectedObservers数组中
    for (CFIndex idx = 0; idx < cnt; idx++) 
        CFRunLoopObserverRef rlo = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
        if (0 != (rlo->_activities & activity) && __CFIsValid(rlo) && !__CFRunLoopObserverIsFiring(rlo)) 
            collectedObservers[obs_cnt++] = (CFRunLoopObserverRef)CFRetain(rlo);
        
    
    //遍历collectedObservers,调用rlo->_callout,外部设置好的CFRunLoopObserverRef回调函数
    /*回顾一下添加obserber监听runloop状态的代码,两部分就对应上了
     CFRunLoopRef runLoop = CFRunLoopGetCurrent();
     CFStringRef runLoopMode = kCFRunLoopDefaultMode;
     //此处传入的block应该就是赋值给rlo->_callout
     CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) 
     );
     //observer被放到了rlm->_observers数组中
     CFRunLoopAddObserver(runLoop, observer, runLoopMode);
     */
    for (CFIndex idx = 0; idx < obs_cnt; idx++) 
        CFRunLoopObserverRef rlo = collectedObservers[idx];
        __CFRunLoopObserverLock(rlo);
        if (__CFIsValid(rlo)) 
            //调用rlo->_callout,外部设置好的CFRunLoopObserverRef回调函数
            __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(rlo->_callout, rlo, activity, rlo->_context.info);
         else 
        
    

__CFRunLoopServiceMachPort

static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy, CFRunLoopRef rl, CFRunLoopModeRef rlm)

这个函数可以监听Mach ports,  soure1、timer、CGD都是借助Mach ports与runloop通信的。

当timeout参数为0时,立刻返回当前是否存在未处理的soure1、timer或GCD任务,通过livePort返回

当timeout参数不为0时,会一直处于监听状态,直到产生一个任务

__CFRunLoopRun

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) 
    uint64_t startTSR = mach_absolute_time();

    //依赖GCDtimer实现超时机制
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    //参数CFTimeInterval seconds就是外部设置的超时时间
    if (seconds <= 0.0)  // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
     else if (seconds <= TIMER_INTERVAL_LIMIT) 
	dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
	timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_retain(timeout_timer);
	timeout_context->ds = timeout_timer;
	dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
	dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
        dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
        uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
        dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
        dispatch_resume(timeout_timer);
     else  // infinite timeout
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    

    Boolean didDispatchPortLastTime = true;
    //retVal只要为0,runloop就一直在当前mode下循环
    int32_t retVal = 0;
    //进入runloop循环
    do 
	__CFPortSet waitSet = rlm->_portSet;

        __CFRunLoopUnsetIgnoreWakeUps(rl);
        //通知observer kCFRunLoopBeforeTimers kCFRunLoopBeforeSources
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) 
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        
        if (rlm->_observerMask & kCFRunLoopBeforeSources) 
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        

	__CFRunLoopDoBlocks(rl, rlm);
        //处理source0,如果有source0被处理,sourceHandledThisLoop = true
        /*returnAfterSourceHandled(stopAfterHandle)参数作用:看了一下__CFRunLoopDoSources0的实现
         好像是如果有一组source0要处理时,如果stopAfterHandle设置为true,则只处理第一个
        */
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) 
            __CFRunLoopDoBlocks(rl, rlm);
        
        
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) 
            msg = (mach_msg_header_t *)msg_buffer;
            //是否有source1或timer或mainDispatch
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL, rl, rlm)) 
                //goto label:handle_msg
                //有source1或timer或mainDispatch
                goto handle_msg;
            
        
        didDispatchPortLastTime = false;
 
    //根据poll决定是否通知kCFRunLoopBeforeWaiting
	if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();

        //进入循环等待
        do 
            msg = (mach_msg_header_t *)msg_buffer;
            //是否有source1或timer或mainDispatch
            //注意poll对timeout参数影响
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm);
            
            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.
                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 
                    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                
             else 
                // Go ahead and leave the inner loop.
                break;
            
         while (1); //循环等待

        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
    //根据poll决定是否通知kCFRunLoopAfterWaiting
	if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        //上面,如果有source1/timer/mainDispatch直接跳转到这里,label:handle_msg handle_msg
        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);

        if (MACH_PORT_NULL == livePort) 
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_NOTHING, rl, rlm, livePort, 0);
            // handle nothing
         else if (livePort == rl->_wakeUpPort) 
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_WAKEUP, rl, rlm, livePort, 0);
            // do nothing on Mac OS
         //有timer要处理
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) 
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_TIMER, rl, rlm, livePort, 0);
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) 
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl); //有timer要处理
            
        
        else if (livePort == dispatchPort) 
            //有dispatch_main_queue任务要处理
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        
        else 
            //处理soure1
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_SOURCE, rl, rlm, 0, 0);
            // Despite the name, this works for windows handles as well
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
        
        
        /* --- BLOCKS --- */
        
#if TARGET_OS_MAC
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
        
	__CFRunLoopDoBlocks(rl, rlm);

    //stopAfterHandle设置为true时,有source0/1被处理了,就退出当前runloop
	if (sourceHandledThisLoop && stopAfterHandle) 
	    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))    //mode中都没有source0、source1、timer了,runloop完成
	    retVal = kCFRunLoopRunFinished;
	

     while (0 == retVal);
    /*retVal != kCFRunLoopRunHandledSource/kCFRunLoopRunTimedOut/ __CFRunLoopUnsetStopped/kCFRunLoopRunFinished runloop就一直在当前mode状态下循环*/
    return retVal;

Mark:

  1. __CFRunLoopDoBlocks: 可以使用 ​CFRunLoopPerformBlock​ 函数往 run loop 中添加 blocks
  1. mach_port机制不仅限于source1唤醒runloop, dispatch_main_queue、timer、CFRunLoopWakeUp等都有对应的port唤醒runloop
  1. __CFRunLoopServiceMachPort函数用于监听port,内部依赖mach_msg函数(MACH_RCV_MSG接收消息)

static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy, CFRunLoopRef rl, CFRunLoopModeRef rlm)

mach_port_name_t  port: 要监听的ports

mach_port_t  livePort: 接收将要处理的msg对应的port

mach_msg_timeout_t  timeout: 在 timeout 之前如果没有读到 msg,当前线程会一直处于休眠状态.传0时不进入睡眠.

CFRunLoopRef rl: runloop对象

CFRunLoopModeRef rlm: mode对象

 

逻辑流程图

 
  1. 逻辑执行都是在beforeSource或afterWating之后的
  2. 以下两种情况下,runloop会跳过Waiting状态
    1. 在一次runloop循环开始是有source0被处理了,poll变量为true .当poll为true时,不会通知before(after)Waiting,同时第二次__CFRunLoopServiceMachPort的timeout为0,也就是立即返回,不睡眠
    2. 在处理source0后,通过mach port发现存在mainDispatch任务,会goto直接到最后的处理阶段
  3. beforeWaiting及afterWaiting之前是“睡眠”状态,属于空闲
  4. timer/mainDispatch/souce1任务每次循环只执行其中一种

以上是关于CFRunLoop源码分析笔记的主要内容,如果未能解决你的问题,请参考以下文章

Java集合源码学习笔记ArrayList分析

Handler机制源码分析笔记

Java笔记---ArrayList源码分析

Java集合源码学习笔记HashMap分析

FreeRTOSFreeRTOS学习笔记— 手写FreeRTOS双向链表/源码分析

方舟编译器学习笔记14 DriverRunner源码分析