iOS源码解析: runloop的运行原理

1,821 阅读41分钟

这部分主要是runloop的源码解析

下边的这张图可以说是讲解runloop最为经典的了,来自于 深入理解RunLoop

接下来,会详细解读源码的一些细节,有些部分的代码会比较长,粘贴在这里也是为了保持其完整。

入口:CFRunLoopRun

void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        // 用DefaultMode启动。
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

// 用指定的mode启动,允许设置超时时间
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

CFRunLoopRunSpecific函数通过第二个参数CFStringRef modeName来指定runloop运行的Mode。这里,使用的modeName参数是一个字符串,而非封装好的RunLoopMode对象。参数seconds即是外部调用时传入的runloop运行时间,决定了runloop能够运行多久,外部传入的,内部使用gcd timer或mk timer来实现。

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    /// 首先根据modeName找到对应的mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    /// 若mode是empty的,即没有Mode Item(source、timer、observer),则返回。
    /// 所以runloop要想存活,必须有mode item存在,或者有block要执行。
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
	    Boolean did = false;
	    if (currentMode) __CFRunLoopModeUnlock(currentMode);
	    __CFRunLoopUnlock(rl);
	    return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }

    /// _per_run_data是runloop每一次run的时候的相关数据。之前已有详细介绍。
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    /// 先将kCFRunLoopEntry状态通知给observer
	if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    /// 正式进入runloop
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    /// runloop退出,将kCFRunLoopExit状态通知给observer
	if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopPopPerRunData(rl, previousPerRun);
	rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

__CFRunLoopFindMode

__CFRunLoopFindMode函数比较长,但是目的很明确:根据runloop对象和modeName查找runloopMode对象,若找不到则新创建一个。

/* call with rl locked, returns mode locked */
static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create) {
    CHECK_FOR_FORK();
    CFRunLoopModeRef rlm;
    struct __CFRunLoopMode srlm;
    // 将srlm指针指向的内存区域的特定长度,设置为0
    memset(&srlm, 0, sizeof(srlm));
    _CFRuntimeSetInstanceTypeIDAndIsa(&srlm, __kCFRunLoopModeTypeID);
    srlm._name = modeName;
    // 从runloop对象的_modes容器中,查询mode对象
    rlm = (CFRunLoopModeRef)CFSetGetValue(rl->_modes, &srlm);
    if (NULL != rlm) {
	    __CFRunLoopModeLock(rlm);
	    return rlm;
    }
    if (!create) {
	    return NULL;
    }
    // 如果没有该mode对象,则新创建一个。
    rlm = (CFRunLoopModeRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopModeTypeID, sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase), NULL);
    if (NULL == rlm) {
	    return NULL;
    }
    __CFRunLoopLockInit(&rlm->_lock);
    rlm->_name = CFStringCreateCopy(kCFAllocatorSystemDefault, modeName);
    rlm->_stopped = false;
    rlm->_portToV1SourceMap = NULL;
    rlm->_sources0 = NULL;
    rlm->_sources1 = NULL;
    rlm->_observers = NULL;
    rlm->_timers = NULL;
    rlm->_observerMask = 0;
    rlm->_portSet = __CFPortSetAllocate();
    rlm->_timerSoftDeadline = UINT64_MAX;
    rlm->_timerHardDeadline = UINT64_MAX;

    kern_return_t ret = KERN_SUCCESS;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    rlm->_timerFired = false;
    // 这个queue,后续有用到?
    rlm->_queue = _dispatch_runloop_root_queue_create_4CF("Run Loop Mode Queue", 0);
    mach_port_t queuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
    if (queuePort == MACH_PORT_NULL) CRASH("*** Unable to create run loop mode queue port. (%d) ***", -1);
    rlm->_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, rlm->_queue);

    __block Boolean *timerFiredPointer = &(rlm->_timerFired);
    dispatch_source_set_event_handler(rlm->_timerSource, ^{
        *timerFiredPointer = true;
    });

    // Set timer to far out there. The unique leeway makes this timer easy to spot in debug output.
    _dispatch_source_set_runloop_timer_4CF(rlm->_timerSource, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 321);
    dispatch_resume(rlm->_timerSource);

    ret = __CFPortSetInsert(queuePort, rlm->_portSet);
    if (KERN_SUCCESS != ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);

#endif
#if USE_MK_TIMER_TOO
    // 这个_timerPort是干啥的?
    rlm->_timerPort = mk_timer_create();
    ret = __CFPortSetInsert(rlm->_timerPort, rlm->_portSet);
    if (KERN_SUCCESS != ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);
#endif
    // runloop对象的_wakeUpPort会用到哪里?
    ret = __CFPortSetInsert(rl->_wakeUpPort, rlm->_portSet);
    if (KERN_SUCCESS != ret) CRASH("*** Unable to insert wake up port into port set. (%d) ***", ret);

#if DEPLOYMENT_TARGET_WINDOWS
    rlm->_msgQMask = 0;
    rlm->_msgPump = NULL;
#endif
    CFSetAddValue(rl->_modes, rlm);
    CFRelease(rlm);
    __CFRunLoopModeLock(rlm);	/* return mode locked */
    return rlm;
}

__CFRunLoopModeIsEmpty

__CFRunLoopModeIsEmpty函数接收runloop对象和runloopMode参数,用于判断RunloopMode是否有对应的Source(也可理解为runloopMode Item、block)。即判断该runloop是否有任务需要执行,没有任务,runloop是没有理由运行的。

// expects rl and rlm locked
static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) {
    CHECK_FOR_FORK();
    if (NULL == rlm) return true;
#if DEPLOYMENT_TARGET_WINDOWS
    if (0 != rlm->_msgQMask) return false;
#endif

    /// 主线程会比较特殊,其mode item肯定不会为空
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) return false; // represents the libdispatch main queue

    /// 判断source0、source1、timer
    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;

    /// 判断block,依次执行runloop中的block任务。
    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;
}

该函数判断是否有block任务的一句代码比较值得注意:

doit = CFEqual(curr->_mode, rlm->_name) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));

条件1(当前runloopMode的名字与block中存储的一致)、条件2(block任务指定于CommonModes中执行,且当前runloopMode的名字在commonModes中)满足其一即可认为该block任务需要执行。

runloop支持嵌套使用

注意下边这段简化的流程:

volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;

/// 运行runloop,从Entry至Exit的所有流程

__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;

这段代码说明了以下三点:

  1. runloop运行之前,会有previous数据(previousPerRun)和mode(previousMode)的保存操作
  2. runloop运行完毕之后,会有previous数据(previousPerRun)和mode(previousMode)的恢复操作
  3. runloop支持嵌套使用:在runloop的运行过程中(如正在处理任务的callout函数)再次运行,则当前runloop的数据和状态被保存,之后再恢复。

而关于上边的第三点,苹果的官方文档也写得很清楚:

Run loops can be run recursively. You can call CFRunLoopRun() or CFRunLoopRunInMode(::_:) from within any run loop callout and create nested run loop activations on the current thread’s call stack. You are not restricted in which modes you can run from within a callout. You can create another run loop activation running in any available run loop mode, including any modes already running higher in the call stack.

那么问题来了,runloop的恢复操作仅仅给perRunData和runloopMode恢复值既可以做到了么???

小结

总结一下CFRunLoopRunSpecific()函数的所做的任务如下:

  1. __CFRunLoopFindMode找到对应的runloopMode
  2. __CFRunLoopModeIsEmpty检查source是否为空
  3. 对runloop做PerRunData和runloopMode的保存操作
  4. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry)
  5. __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
  6. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
  7. 对runloop做PerRunData和runloopMode的恢复操作

__CFRunLoopDoObservers函数将kCFRunLoopEntry这个状态,同步给各个Observer去执行各自的回调函数。

if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

接下来开始研究真正的runloop运行入口:__CFRunLoopRun。

真正的入口:__CFRunLoopRun

__CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); 函数接收了不少参数。

其函数原型如下:

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) __attribute__((noinline));

这个函数可以说是非常非常的长了。。。不过这就是runloop运行的核心了。这里贴出全部代码以保持完整,源码解读也会添加到注释中。

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    uint64_t startTSR = mach_absolute_time();

    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
	    return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
	    rlm->_stopped = false;
	    return kCFRunLoopRunStopped;
    }

    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    /// 判断是主线程队列main_queue。
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    /// 这里的 dispatchPort 是什么?
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();

#if USE_DISPATCH_SOURCE_FOR_TIMERS
    /// 是否使用了GCD timer?
    mach_port_name_t modeQueuePort = MACH_PORT_NULL;
    if (rlm->_queue) {
        modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
        if (!modeQueuePort) {
            CRASH("Unable to get port for run loop mode queue (%d)", -1);
        }
    }
#endif

    /// 这段代码表明,runloop设置的时间间隔,其实也是使用的GCD timer来实现的。dispatch_source_t与runloop source是否有关???
    /// GCD timer是依赖于内核的,所以非常精准。不受runloop影响。
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    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;
	    timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
	    timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
        /// 将timeout_context关联到timer中
	    dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
        /// __CFRunLoopTimeout是一个函数指针,内部会调用CFRunLoopWakeUp(context->rl);用于唤醒runloop
	    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;
    int32_t retVal = 0;
    do {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
        voucher_t voucherCopy = NULL;
#endif
        uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        mach_msg_header_t *msg = NULL;
        mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
        HANDLE livePort = NULL;
        Boolean windowsMessageReceived = false;
#endif
	    __CFPortSet waitSet = rlm->_portSet;

        __CFRunLoopUnsetIgnoreWakeUps(rl);

        /// 注意:runloop即将开始做事,处理timer、source、block等。
        /// 将kCFRunLoopBeforeTimers状态通知给observer
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        /// 将kCFRunLoopBeforeSources状态通知给observer
        /// 即将触发source0回调,非port
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        /// 执行加入到runloop中的block
	    __CFRunLoopDoBlocks(rl, rlm);

        // 处理Sources0事件,那Timer事件在哪里处理的???在runloop唤醒之后,为何要将timer放到runloop被唤醒之后呢?
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            /// 这里为啥又有一个__CFRunLoopDoBlocks
            __CFRunLoopDoBlocks(rl, rlm);
	    }

        /*
         每次 loop 如果处理了 source0 任务,那么 poll 值会为 true,直接的影响是不会 DoObservers-BeforeWaiting 和 DoObservers-AfterWaiting,也就是说 runloop 会直接进入睡眠,而且不会告知 BeforeWaiting 和 AfterWaiting 这两个 activity。所以你看,有些情况下,可能 runloop 经过了几个 loop,但你注册的 observer 却不会收到 callback。
         */
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            msg = (mach_msg_header_t *)msg_buffer;
            /// 有mach port即为source1事件,对应一个mach msg,处理该source1事件,即该mach msg。
            /// 如用户操作、网络等都会通过port。
            /// 为何这里是dispatchPort?
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
#elif DEPLOYMENT_TARGET_WINDOWS
            if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                goto handle_msg;
            }
#endif
        }

        /// 以上代码就是runloop运行做的事情,这之后就是runloop即将进入休眠状态了。
        /// 而如果有使用port,则通过handle_msg跳转到__CFRunLoopSetIgnoreWakeUps,即runloop唤醒

        didDispatchPortLastTime = false;

        /// 注意:runloop的事情已经做完,即将进入休眠
        /// 休眠,即执行do-while(1)操作,在其中执行mach_msg_trap()切换到内核态。之后,则只能通过source1主动唤醒runloop。
        /// 用户触摸事件,会包装成一个source1事件,通过port,mach_msg传递给Mach底层,然后调用trap()函数完成OS状态切换。
        /// 将kCFRunLoopBeforeWaiting状态通知给observer
	    if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        /// runloop开始休眠,即后边的do-while(1)循环。
	    __CFRunLoopSetSleeping(rl);
	    // do not do any user callouts after this point (after notifying of sleeping)

        // Must push the local-to-this-activation ports in on every loop
        // iteration, as this mode could be run re-entrantly and we don't
        // want these ports to get serviced.

        __CFPortSetInsert(dispatchPort, waitSet);

	    __CFRunLoopModeUnlock(rlm);
	    __CFRunLoopUnlock(rl);

        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        /// 这里的do-while(1)仅是循环,那实际上runloop如何做到休眠???
        do {
            if (kCFUseCollectableAllocator) {
                // objc_clear_stack(0);
                // <rdar://problem/16393959>
                memset(msg_buffer, 0, sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;
/*
这个__CFRunLoopServiceMachPort函数很关键。调用mach_msg,等待接收mach port的msg。
线程即将进入休眠,直到被如下事件之一唤醒:
1,一个基于port的source1事件
2,一个timer的触发时间
3,runloop自身的超时时间到了
4,手动唤醒,即使用WakeUp函数调用。

即该函数执行后,runloop即休眠、线程休眠,则这行代码之后的都不会执行的。直到runloop被唤醒,即线程被唤醒!!!这一点很重要。
这里的waitSet,即函数第一个参数说明了什么?进入休眠,但是可以接收waitSet端口的mach msg。
*/
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

            /// 上边runloop已经进入休眠了,那如何唤醒,如何结束while(1)???
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                /// 这个while循环又是做什么???以及里边的break有啥作业,没看明白?
                // 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);
#else
        if (kCFUseCollectableAllocator) {
            // objc_clear_stack(0);
            // <rdar://problem/16393959>
            memset(msg_buffer, 0, sizeof(msg_buffer));
        }
        msg = (mach_msg_header_t *)msg_buffer;
        /// 调用mach_msg,等待接收mach port的msg。线程休眠
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif


#elif DEPLOYMENT_TARGET_WINDOWS
        // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
        __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif

        /// 一旦退出了上边的do-while(1),则runloop被唤醒了
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
        /// 这里有统计runloop的休眠时间,这个时间是否可以用来做一些什么事情呢?
        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));

        // Must remove the local-to-this-activation ports in on every loop
        // iteration, as this mode could be run re-entrantly and we don't
        // want these ports to get serviced. Also, we don't want them left
        // in there if this function returns.

        __CFPortSetRemove(dispatchPort, waitSet);

        __CFRunLoopSetIgnoreWakeUps(rl);

        // __CFRunLoopSetSleeping(rl);与__CFRunLoopUnsetSleeping(rl);一一对应。
        // runloop的休眠与唤醒
        // user callouts now OK again
	    __CFRunLoopUnsetSleeping(rl);

        /// 至于线程是如何被唤醒的,则不用管。内核自行处理的。
        /// 将kCFRunLoopAfterWaiting同步给observer:刚从休眠状态中唤醒
	    if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        /// 唤醒后,收到消息即处理消息;即通过port唤醒runloop,可以直接goto语句到这里。
        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被唤醒之后,继续做事情。为何不与上边的代码复用?
            /// 因为timer被唤醒
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            /// 触发timer的回调函数
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
#if USE_MK_TIMER_TOO
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
            // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
            void *msg = 0;
#endif
            /// 这里的dispatchPort是否意味着就是主线程?是的!
            /// 如果有dispatch到main queue的block,即执行。
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            /// 注册一个回调
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            /// 这里的SOURCE是source0还是source1??source1,基于port
            /// 能主动唤醒
            CFRUNLOOP_WAKEUP_FOR_SOURCE();

            // If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
            voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);

            // 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;
                /// 处理source1事件
		        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);
		        }
#elif DEPLOYMENT_TARGET_WINDOWS
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
	        }

            // Restore the previous voucher
            _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);

        }
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif

        /// 执行加入到runloop的block。
	    __CFRunLoopDoBlocks(rl, rlm);


	    if (sourceHandledThisLoop && stopAfterHandle) {
            /// 进入runloop时,参数说处理完事件就返回
	        retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            /// runloop超时了
            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 item一个都没有了
	        retVal = kCFRunLoopRunFinished;
	    }

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);
#endif
    /// retVal即为runloop处理完了所有事件,又继续进入一次循环,do-while(1)。
    } while (0 == retVal);

    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }

    return retVal;
}

这个函数看起来确实比较累,抓住几个核心的点,再梳理起来就比较容易。

外层循环do-while(0 == retVal)

外层循环即为runloop的一次loop的完整流程。

retVal的值一旦非零,这个外层do-while循环则会退出,__CFRunLoopRun函数返回一个非零值,也意味着runloop的运行结束。而retVal变成非零的情况只有以下几种:

/* Reasons for CFRunLoopRunInMode() to Return */
typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
    kCFRunLoopRunFinished = 1,
    kCFRunLoopRunStopped = 2,
    kCFRunLoopRunTimedOut = 3,
    kCFRunLoopRunHandledSource = 4
};

仔细看下边这段源码

if (sourceHandledThisLoop && stopAfterHandle) {
    // 进入runloop时,参数说处理完事件就返回
    retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
    // runloop超时了
    retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
    __CFRunLoopUnsetStopped(rl);
    // 被外部调用者强行停止了
    retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
    rlm->_stopped = false;
    // 被外部调用者强行停止了
    // 这两者有啥区别?
    // __CFRunLoopIsStopped 中 return (rl->_perRunData->stopped) ? true : false;
    retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
      // mode item一个都没有了,正常结束
   retVal = kCFRunLoopRunFinished;
}

结论已经很明显了:

  1. stopAfterHandle是__CFRunLoopRun函数的第四个参数,也是CFRunLoopRunSpecific函数的第四个参数returnAfterSourceHandled。CFRunLoopRun函数调用时,该参数为false,所以NSRunLoop的run:一旦执行则无法停止。而CFRunLoopRunInMode函数调用时,该参数是在调用NSRunLoop的runUntilDate:或runMode:beforeDate:时进行相应判断进而设置的。所以符合条件的话,本次loop的任务执行完毕(sourceHandledThisLoop)后就退出。
  2. runloop超时是通过timeout_context来进行判断的。
  3. __CFRunLoopIsStopped和rlm->_stopped分别是啥???是外部强行停止?暂时不清楚如何停止???
  4. 再次调用__CFRunLoopModeIsEmpty判断runloopMode的source是否为空,没有则结束runloop。

内层循环do-while(1)

这个内层的do-while(1)循环,就是runloop进入休眠状态。 做的事情很明确,就是调用__CFRunLoopServiceMachPort函数。

会执行以下代码进入休眠状态(内核态)。一旦这个内层的do-while(1)退出循环,即为runloop唤醒

do {
    if (kCFUseCollectableAllocator) {
        // objc_clear_stack(0);
        // <rdar://problem/16393959>
        memset(msg_buffer, 0, sizeof(msg_buffer));
    }
    msg = (mach_msg_header_t *)msg_buffer;

    __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

    // 上边runloop已经进入休眠了,那如何唤醒,如何结束while(1)???
    if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
        // 这个while循环又是做什么???以及里边的break有啥作业,没看明白?
        // 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);

这个__CFRunLoopServiceMachPort函数很关键。调用mach_msg,等待接收mach port的msg。线程即将进入休眠,直到被如下事件之一唤醒:

  1. 一个基于port的source1事件
  2. 一个timer的触发时间
  3. runloop自身的超时时间到了
  4. 手动唤醒。

runloop进入休眠

__CFRunLoopServiceMachPort即是mach msg发挥作用的地方了。其中会执行mach_msg函数,使得系统从用户态切换至内核态。且mach_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) {
    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;
    // 又是一个循环
    for (;;) {		/* In that sleep of death what nightmares may come ... */
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0;
        // msg的port方向,若从_dispatch_get_main_queue_port_4CF到MACH_PORT_NULL,则休眠???
        msg->msgh_local_port = port;
        msg->msgh_remote_port = MACH_PORT_NULL;
        msg->msgh_size = buffer_size;
        msg->msgh_id = 0;
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }

        ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);

        // Take care of all voucher-related work right after mach_msg.
        // If we don't release the previous voucher we're going to leak it.
        voucher_mach_msg_revert(*voucherState);

        // Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
        *voucherState = voucher_mach_msg_adopt(msg);

        if (voucherCopy) {
            if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
                // Caller requested a copy of the voucher at this point. By doing this right next to mach_msg we make sure that no voucher has been set in between the return of mach_msg and the use of the voucher copy.
                // CFMachPortBoost uses the voucher to drop importance explicitly. However, we want to make sure we only drop importance for a new voucher (not unchanged), so we only set the TSD when the voucher is not state_unchanged.
                *voucherCopy = voucher_copy();
            } else {
                *voucherCopy = NULL;
            }
        }

        CFRUNLOOP_WAKEUP(ret);
        if (MACH_MSG_SUCCESS == ret) {
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
        if (MACH_RCV_TIMED_OUT == ret) {
            if (!originalBuffer) free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        }
        if (MACH_RCV_TOO_LARGE != ret) break;
        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        if (originalBuffer) *buffer = NULL;
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
    }
    HALT;
    return false;
}

这段代码里边有个for循环,其中不断构建mach_msg对象,执行mach_msg()函数。之前有讲到mach_msg()内部实际上是调用了mach_msg_trap()系统调用,所以,这个函数里边会触发操作系统状态的改变,即从用户态切换至内核态,runloop进入休眠。

ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);

如果有msg要处理,跳转到handle_msg。这一步对应着就是有Source1要处理。

// 首次didDispatchPortLastTime为true,不会执行
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
    msg = (mach_msg_header_t *)msg_buffer;
    if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
        goto handle_msg;
    }
}

关于休眠,要注意的是,runloop休眠后,线程也进入休眠,CPU不会做任何事情了,之后的代码什么的都不会执行。而一旦使用mach_msg()函数给mach内核发送了休眠的消息,系统进入了内核态,之后我们能做的只有等待而已。静静地等待runloop被唤醒而已

runloop被唤醒

退出内层循环,即为runloop被唤醒了。有两种情况???

modeQueuePort和livePort判断,或者rlm->_timerFired = false; 为啥???

// 上边runloop已经进入休眠了,那如何唤醒,如何结束while(1)???
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
    // 这个while循环又是做什么???以及里边的break有啥作业,没看明白?
    // 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;
}

唤醒条件:

  1. 给于port的source1事件
  2. Timer时间到了
  3. runloop超时???
  4. 被调用者唤醒?

小结

知道了 内层do-while(1)是系统触发状态切换而进入休眠状态 这一个关键点之后,则该内层循环之前和之后代码各自的作用,根据runloop的运行流程图就很容易理解了。

  1. 内层循环do-while(1)之前:处理Timer、Source、Block任务,之后runloop即进入休眠状态
  2. 内层循环do-while(1)之后:runloop被唤醒,处理一些任务(这里就是Source1的任务了)

内层循环do-while(1)之前

这一部分是runloop执行任务的地方,也是runloop进入休眠之前执行的任务,流程大概如下:

  1. 设置runloop的状态
  2. 将BeforeTimers和BeforeSources两个状态通知到Observer
  3. 执行block任务
  4. 执行Sources0事件
  5. 将BeforeWaiting状态通知到Observer
  6. 设置runloop的状态

关于这个阶段,有一个疑问:发送了BeforeTimers状态给Observer,但是没有执行Timer事件,这是为什么?

实际上,Timer事件是在runloop先进入休眠、再被唤醒后执行的,即在内层循环do-while(1)之后阶段中。我们可以从__CFRunLoopDoTimers函数的调用时机看出来。

关于runloop执行任务的更详细解读,会在后续章节 真正做事情的地方 __CFRunLoopDoXXX 中体现。

内层循环do-while(1)之后

这个阶段是runloop被唤醒之后执行任务的阶段。

此时,runloop被唤醒,会先统计当次runloop的休眠时间(_sleepTime字段):***rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));***。然后设置唤醒后的一些状态(__CFRunLoopSetIgnoreWakeUps、__CFRunLoopUnsetSleeping)。将AfterWaiting状态通知给Observer:

if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

接下来,就是runloop被唤醒后,处理任务的地方了。

  1. 判断livePort和modeQueuePort
  2. 判断_timerPort,执行timer回调
  3. 判断dispatchPort
  4. 以上都不是,那么就是Source1事件了。先根据runloop、runloopMode和livePort查找对应的Source1,然后调用__CFRunLoopDoSource1函数来执行任务。如果有回复的mach msg,会再通过mach_msg函数将回复消息发送到指定mach port。这就是mach核心的线程通信方式。
  5. 最后一步,再次执行block任务。然后,继续外层do-while循环,继续执行下一次loop。

真正做事情的地方 __CFRunLoopDoXXX

runloop源码中会有一系列 __CFRunLoopDo 开头的函数:

__CFRunLoopDoBlocks(rl, rlm);
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
__CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

这些函数是runloop执行任务的关键,后边会详细介绍。注意,每个函数接收的参数都有runloop对象和runloopMode对象,这也验证了runloopMode之间相互隔离,各自处理各自任务的基本事实。

下边对于各个函数的讲解顺序与其实际执行顺序无关。

处理Block任务 __CFRunLoopDoBlocks

__CFRunLoopDoBlocks(rl, rlm)会执行加到runloop中的block任务,对_block_item的链表结构进行遍历,触发 CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK(block); 来执行block。

// Call with rl and rlm locked
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) {
    if (!rl->_blocks_head) return false;
    if (!rlm || !rlm->_name) return false;

    Boolean did = false;
    struct _block_item *head = rl->_blocks_head;
    struct _block_item *tail = rl->_blocks_tail;
    rl->_blocks_head = NULL;
    rl->_blocks_tail = NULL;
    CFSetRef commonModes = rl->_commonModes;
    CFStringRef curMode = rlm->_name;
    /// 从rl、rlm中取出所需数据后,即可解锁
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);

    struct _block_item *prev = NULL;
    struct _block_item *item = head;
    while (item) {
        struct _block_item *curr = item;
        item = item->_next;
	    Boolean doit = false;
	    if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
	        doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        } else {
	        doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
	    }
	    if (!doit) prev = curr;
	    if (doit) {
	        if (prev) prev->_next = item;
	        if (curr == head) head = item;
	        if (curr == tail) tail = prev;
            /// _block_item->_block这个字段是哪里传入的?
	        void (^block)(void) = curr->_block;
            CFRelease(curr->_mode);
            free(curr);
	        if (doit) {
                /// runloop这里的block是什么意思???
                __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
	            did = true;
	        }
            Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
	    }
    }
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);
    if (head) {
	tail->_next = rl->_blocks_head;
	rl->_blocks_head = head;
        if (!rl->_blocks_tail) rl->_blocks_tail = tail;
    }
    return did;
}

实际执行block的callout函数原型如下:

static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void)) {
    if (block) {
        block();
    }
    asm __volatile__(""); // thwart tail-call optimization
}

_block_item

每个runloop对象中都有两个字段_blocks_head、_blocks_tail,都是_block_item结构体。_block_item这个结构体在runloop中显然构成了一个链表结构,每个节点都存储了一个_block对象。

struct _block_item {
    struct _block_item *_next;
    CFTypeRef _mode;	// CFString or CFSet
    void (^_block)(void);
};

那么,runloop对象的这两个字段是哪里填充的呢?

block是如何加到runloop中的

CFRunLoopPerformBlock函数将外部传入的block包装成_block_item结构,放到runloop对象的_blocks_head和_blocks_tail字段中。

void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void)) {
    CHECK_FOR_FORK();
    if (CFStringGetTypeID() == CFGetTypeID(mode)) {
	    mode = CFStringCreateCopy(kCFAllocatorSystemDefault, (CFStringRef)mode);
        __CFRunLoopLock(rl);
	    // ensure mode exists
        CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, (CFStringRef)mode, true);
        if (currentMode) __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopUnlock(rl);
    } else if (CFArrayGetTypeID() == CFGetTypeID(mode)) {
        CFIndex cnt = CFArrayGetCount((CFArrayRef)mode);
	    const void **values = (const void **)malloc(sizeof(const void *) * cnt);
        CFArrayGetValues((CFArrayRef)mode, CFRangeMake(0, cnt), values);
	    mode = CFSetCreate(kCFAllocatorSystemDefault, values, cnt, &kCFTypeSetCallBacks);
        __CFRunLoopLock(rl);
	    // ensure modes exist
	    for (CFIndex idx = 0; idx < cnt; idx++) {
            CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, (CFStringRef)values[idx], true);
            if (currentMode) __CFRunLoopModeUnlock(currentMode);
	    }
        __CFRunLoopUnlock(rl);
	    free(values);
    } else if (CFSetGetTypeID() == CFGetTypeID(mode)) {
        CFIndex cnt = CFSetGetCount((CFSetRef)mode);
	    const void **values = (const void **)malloc(sizeof(const void *) * cnt);
        CFSetGetValues((CFSetRef)mode, values);
	    mode = CFSetCreate(kCFAllocatorSystemDefault, values, cnt, &kCFTypeSetCallBacks);
        __CFRunLoopLock(rl);
	    // ensure modes exist
	    for (CFIndex idx = 0; idx < cnt; idx++) {
            CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, (CFStringRef)values[idx], true);
            if (currentMode) __CFRunLoopModeUnlock(currentMode);
	    }
        __CFRunLoopUnlock(rl);
	    free(values);
    } else {
	    mode = NULL;
    }
    /// 对block做了copy操作。
    block = Block_copy(block);
    if (!mode || !block) {
	    if (mode) CFRelease(mode);
	    if (block) Block_release(block);
	    return;
    }
    __CFRunLoopLock(rl);
    struct _block_item *new_item = (struct _block_item *)malloc(sizeof(struct _block_item));
    new_item->_next = NULL;
    new_item->_mode = mode;
    new_item->_block = block;
    /// runloop中的_block_item构成了链表结构,新的block加到链表尾部。
    if (!rl->_blocks_tail) {
	    rl->_blocks_head = new_item;
    } else {
	    rl->_blocks_tail->_next = new_item;
    }
    rl->_blocks_tail = new_item;
    __CFRunLoopUnlock(rl);
}

看这段代码的最后一部分,runloop中的_block_item构成了链表结构,链表头尾分别是_blocks_head和_blocks_tail,新的block则加到链表尾部。

那CFRunLoopPerformBlock()函数是在什么场景下调用呢?答案就是CFRunLoopPerformBlock这个对外接口,即:

CFRunLoopPerformBlock -> 添加block到runloop中 -> runloop唤醒执行任务 -> __CFRunLoopDoBlocks -> __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block) -> block()

于是,一切都明白了。runloop可以直接用来执行block,只不过平常基本使用不到。

/// CFRunLoopPerformBlock函数会对block进行copy操作。
void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void));
Enqueues a block object on a given runloop to be executed as the runloop cycles in specified modes.

When the runloop runs in the specified mode, the block object is executed. You can use this function as a means to offload work to another thread similar to Cocoa’s performSelector:onThread:withObject:waitUntilDone: and related methods. You can also use it as an alternative to mechanisms such as putting a CFRunLoopTimer in the other thread's run loop, or using CFMessagePort to pass information between threads.

This method enqueues the block only and does not automatically wake up the specified run loop. Therefore, execution of the block occurs the next time the run loop wakes up to handle another input source. If you want the work performed right away, you must explicitly wake up that thread using the CFRunLoopWakeUp function.

CFRunLoopPerformBlock仅仅将block丢到指定队列中,但是不会主动唤醒runloop。如果runloop正在休眠,则该block的运行会等到runloop被其他source唤醒的时候。当然可以使用CFRunLoopWakeUp函数来唤醒runloop。

使用方式如下,不过CFRunLoopPerformBlock()一般场景下很少会用到,可用于将block丢到指定runloop的特定Mode下执行,

CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{
    NSLog(@"CFRunLoopPerformBlock kCFRunLoopCommonModes");
});

处理Timer任务 __CFRunLoopDoTimers

__CFRunLoopDoTimers函数会对加到runloopMode中的所有timer进行遍历,筛选出执行时间到了的timer,调用 __CFRunLoopDoTimer(rl, rlm, rlt) 函数执行timer的回调函数。

// rl and rlm are locked on entry and exit
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {	/* DOES CALLOUT */
    Boolean timerHandled = false;
    CFMutableArrayRef timers = NULL;
    for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);

        if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
            if (rlt->_fireTSR <= limitTSR) {
                if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                CFArrayAppendValue(timers, rlt);
            }
        }
    }

    for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    if (timers) CFRelease(timers);
    return timerHandled;
}

__CFRunLoopDoTimer

__CFRunLoopDoTimer函数的代码就比较多了。

// mode and rl are locked on entry and exit
static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) {	/* DOES CALLOUT */
    Boolean timerHandled = false;
    uint64_t oldFireTSR = 0;

    /* Fire a timer */
    CFRetain(rlt);
    __CFRunLoopTimerLock(rlt);

    if (__CFIsValid(rlt) && rlt->_fireTSR <= mach_absolute_time() && !__CFRunLoopTimerIsFiring(rlt) && rlt->_runLoop == rl) {
        void *context_info = NULL;
        void (*context_release)(const void *) = NULL;
        if (rlt->_context.retain) {
            context_info = (void *)rlt->_context.retain(rlt->_context.info);
            context_release = rlt->_context.release;
        } else {
            context_info = rlt->_context.info;
        }
        Boolean doInvalidate = (0.0 == rlt->_interval);
	__CFRunLoopTimerSetFiring(rlt);
        // Just in case the next timer has exactly the same deadlines as this one, we reset these values so that the arm next timer code can correctly find the next timer in the list and arm the underlying timer.
        rlm->_timerSoftDeadline = UINT64_MAX;
        rlm->_timerHardDeadline = UINT64_MAX;
        __CFRunLoopTimerUnlock(rlt);
	__CFRunLoopTimerFireTSRLock();
	oldFireTSR = rlt->_fireTSR;
	__CFRunLoopTimerFireTSRUnlock();

        __CFArmNextTimerInMode(rlm, rl);

	__CFRunLoopModeUnlock(rlm);
	__CFRunLoopUnlock(rl);
	__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
	CHECK_FOR_FORK();
        if (doInvalidate) {
            CFRunLoopTimerInvalidate(rlt);      /* DOES CALLOUT */
        }
        if (context_release) {
            context_release(context_info);
        }
	__CFRunLoopLock(rl);
	__CFRunLoopModeLock(rlm);
        __CFRunLoopTimerLock(rlt);
	timerHandled = true;
	__CFRunLoopTimerUnsetFiring(rlt);
    }
    if (__CFIsValid(rlt) && timerHandled) {
        /* This is just a little bit tricky: we want to support calling
         * CFRunLoopTimerSetNextFireDate() from within the callout and
         * honor that new time here if it is a later date, otherwise
         * it is completely ignored. */
        if (oldFireTSR < rlt->_fireTSR) {
            /* Next fire TSR was set, and set to a date after the previous
            * fire date, so we honor it. */
            __CFRunLoopTimerUnlock(rlt);
            // The timer was adjusted and repositioned, during the
            // callout, but if it was still the min timer, it was
            // skipped because it was firing.  Need to redo the
            // min timer calculation in case rlt should now be that
            // timer instead of whatever was chosen.
            __CFArmNextTimerInMode(rlm, rl);
        } else {
	    uint64_t nextFireTSR = 0LL;
            uint64_t intervalTSR = 0LL;
            if (rlt->_interval <= 0.0) {
            } else if (TIMER_INTERVAL_LIMIT < rlt->_interval) {
        	intervalTSR = __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
            } else {
        	intervalTSR = __CFTimeIntervalToTSR(rlt->_interval);
            }
            if (LLONG_MAX - intervalTSR <= oldFireTSR) {
                nextFireTSR = LLONG_MAX;
            } else {
                if (intervalTSR == 0) {
                    // 15304159: Make sure we don't accidentally loop forever here
                    CRSetCrashLogMessage("A CFRunLoopTimer with an interval of 0 is set to repeat");
                    HALT;
                }
                uint64_t currentTSR = mach_absolute_time();
                nextFireTSR = oldFireTSR;
                while (nextFireTSR <= currentTSR) {
                    nextFireTSR += intervalTSR;
                }
            }
            CFRunLoopRef rlt_rl = rlt->_runLoop;
            if (rlt_rl) {
                CFRetain(rlt_rl);
		CFIndex cnt = CFSetGetCount(rlt->_rlModes);
		STACK_BUFFER_DECL(CFTypeRef, modes, cnt);
		CFSetGetValues(rlt->_rlModes, (const void **)modes);
		// To avoid A->B, B->A lock ordering issues when coming up
		// towards the run loop from a source, the timer has to be
		// unlocked, which means we have to protect from object
		// invalidation, although that's somewhat expensive.
		for (CFIndex idx = 0; idx < cnt; idx++) {
		    CFRetain(modes[idx]);
		}
		__CFRunLoopTimerUnlock(rlt);
		for (CFIndex idx = 0; idx < cnt; idx++) {
		    CFStringRef name = (CFStringRef)modes[idx];
		    modes[idx] = (CFTypeRef)__CFRunLoopFindMode(rlt_rl, name, false);
		    CFRelease(name);
		}
		__CFRunLoopTimerFireTSRLock();
		rlt->_fireTSR = nextFireTSR;
                rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
		for (CFIndex idx = 0; idx < cnt; idx++) {
		    CFRunLoopModeRef rlm = (CFRunLoopModeRef)modes[idx];
		    if (rlm) {
                        __CFRepositionTimerInMode(rlm, rlt, true);
		    }
		}
		__CFRunLoopTimerFireTSRUnlock();
		for (CFIndex idx = 0; idx < cnt; idx++) {
		    __CFRunLoopModeUnlock((CFRunLoopModeRef)modes[idx]);
		}
		CFRelease(rlt_rl);
	    } else {
		__CFRunLoopTimerUnlock(rlt);
		__CFRunLoopTimerFireTSRLock();
		rlt->_fireTSR = nextFireTSR;
                rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
		__CFRunLoopTimerFireTSRUnlock();
            }
        }
    } else {
        __CFRunLoopTimerUnlock(rlt);
    }
    CFRelease(rlt);
    return timerHandled;
}

这里,跟runloop处理block的逻辑类似,也有一个引人注目的函数调用 CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION(rlt->_callout, rlt, context_info); ,其函数原型为:

static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) {
    if (func) {
        func(timer, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}

第一个参数CFRunLoopTimerCallBack func,即为timer指定的回调函数。第三个参数info可以用于传递参数。

timer是如何加到runloop中的

CFRunLoopAddTimer函数用于将timer添加到runloop中,即添加到指定runloopMode的mode item容器中。

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    __CFRunLoopLock(rl);
    // 若是commonMode
    if (modeName == kCFRunLoopCommonModes) {
        // 首先提取runloop的commonModes,字符串
	    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	    if (NULL == rl->_commonModeItems) {
            // 设置runloop的commonModeItems,懒加载
	        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	    }
        // 把timer加到commonModeItems集合中。
	    CFSetAddValue(rl->_commonModeItems, rlt);
	    if (NULL != set) {
	        CFTypeRef context[2] = {rl, rlt};
	        /* add new item to all common-modes */
            // 对commonModes中的每一个元素,都调用函数__CFRunLoopAddItemToCommonModes
            // 这样,就能对多个mode都添加同一个timer。
            // 即,通过commonMode将timer同步到多个mode中。
            // 目前已知的默认被打上commonMode标记的就是DefaultMode和UITrackingMode。
	        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
	        CFRelease(set);
	    }
    } else {
        //非commonMode,而是被打上了commonMode标记的一个实际mode
	    CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
	    if (NULL != rlm) {
            if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                // 将timer添加到最终runloop的mode对应的timers数组中。
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
            }
	    }
	    if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            __CFRunLoopTimerLock(rlt);
            if (NULL == rlt->_runLoop) {
		        rlt->_runLoop = rl;
  	        } else if (rl != rlt->_runLoop) {
                __CFRunLoopTimerUnlock(rlt);
	            __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
		        return;
	        }
  	        CFSetAddValue(rlt->_rlModes, rlm->_name);
            __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopTimerFireTSRLock();
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                // Normally we don't do this on behalf of clients, but for
                // backwards compatibility due to the change in timer handling...
                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
            }
	    }
        if (NULL != rlm) {
	        __CFRunLoopModeUnlock(rlm);
	    }
    }
    __CFRunLoopUnlock(rl);
}

使用 CFSetAddValue(rlt->_rlModes, rlm->_name);__CFRepositionTimerInMode(rlm, rlt, false); 函数将timer加到了指定runloopMode中了。__CFRepositionTimerInMode函数会执行 CFArrayInsertValueAtIndex(timerArray, newIdx, rlt); 将timer加到runloopMode的_timers容器中,然后调用 __CFArmNextTimerInMode(rlm, rlt->_runLoop); 函数将timer装载好。

所以,runloop timer的调用链就很直观了:

add timer to runloop for mode -> runloop被唤醒执行任务 -> __CFRunLoopDoTimer -> __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info) -> 执行timer的回调函数func(timer, info)

这个函数涉及到了CommonModes的不少代码,对于理解CommonModes相当有帮助。同时可以看出,timer并不关心runloop的activity,仅仅是在runloop被唤醒后,判断timer的触发时机到了则触发回调即可。执行完回调之后,会有设置下一次触发时机的操作。

处理Sources0任务 __CFRunLoopDoSources0

__CFRunLoopDoSources0(rl, rlm, stopAfterHandle);用于处理Source0任务。

Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
    // 这里为啥又有一个DoBlocks???
    __CFRunLoopDoBlocks(rl, rlm);
}

其函数源码为:

/* rl is locked, rlm is locked on entrance and exit */
static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) __attribute__((noinline));
static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) {	/* DOES CALLOUT */
    CHECK_FOR_FORK();
    CFTypeRef sources = NULL;
    Boolean sourceHandled = false;

    /* Fire the version 0 sources */
    if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) {
        // 对该runloop的mode中的sources0,执行__CFRunLoopCollectSources0函数。
	    CFSetApplyFunction(rlm->_sources0, (__CFRunLoopCollectSources0), &sources);
    }
    if (NULL != sources) {
	    __CFRunLoopModeUnlock(rlm);
	    __CFRunLoopUnlock(rl);
	    // sources is either a single (retained) CFRunLoopSourceRef or an array of (retained) CFRunLoopSourceRef
	    if (CFGetTypeID(sources) == CFRunLoopSourceGetTypeID()) {
	        CFRunLoopSourceRef rls = (CFRunLoopSourceRef)sources;
	        __CFRunLoopSourceLock(rls);
            if (__CFRunLoopSourceIsSignaled(rls)) {
	            __CFRunLoopSourceUnsetSignaled(rls);
	            if (__CFIsValid(rls)) {
	                __CFRunLoopSourceUnlock(rls);
                    // 调用source0中设置的perform回调函数
                    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context.version0.perform, rls->_context.version0.info);
	                CHECK_FOR_FORK();
	                sourceHandled = true;
	            } else {
	                __CFRunLoopSourceUnlock(rls);
	            }
            } else {
                __CFRunLoopSourceUnlock(rls);
            }
	    } else {
	        CFIndex cnt = CFArrayGetCount((CFArrayRef)sources);
	        CFArraySortValues((CFMutableArrayRef)sources, CFRangeMake(0, cnt), (__CFRunLoopSourceComparator), NULL);
	        for (CFIndex idx = 0; idx < cnt; idx++) {
		        CFRunLoopSourceRef rls = (CFRunLoopSourceRef)CFArrayGetValueAtIndex((CFArrayRef)sources, idx);
		        __CFRunLoopSourceLock(rls);
                if (__CFRunLoopSourceIsSignaled(rls)) {
		            __CFRunLoopSourceUnsetSignaled(rls);
		            if (__CFIsValid(rls)) {
		                __CFRunLoopSourceUnlock(rls);
                        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context.version0.perform, rls->_context.version0.info);
		                CHECK_FOR_FORK();
		                sourceHandled = true;
		            } else {
		                __CFRunLoopSourceUnlock(rls);
		            }
                } else {
                    __CFRunLoopSourceUnlock(rls);
                }
		        if (stopAfterHandle && sourceHandled) {
		            break;
		        }
	        }
	    }
	    CFRelease(sources);
	    __CFRunLoopLock(rl);
	    __CFRunLoopModeLock(rlm);
    }
    return sourceHandled;
}

source的属性中有一个_context至关重要,CFRunLoopSourceContext中存储的就是source的回调函数,已经回调函数可以接收的参数info。

union {
	CFRunLoopSourceContext version0;	/* immutable, except invalidation */
    CFRunLoopSourceContext1 version1;	/* immutable, except invalidation */
} _context;

其中会调用 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION(rls->_context.version0.perform, rls->_context.version0.info); 以执行任务。该函数的参数perform即为回调函数,参数info即为回调函数可以接收的参数。

static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) {
    if (perform) {
        perform(info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}

Source0是如何唤醒runloop的

执行source0的任务时,source0必须要先被设置为Signal,然后手动调用wakeUp函数来唤醒runloop,进而才能符合其callout函数的调用条件。

void CFRunLoopSourceSignal(CFRunLoopSourceRef rls) {
    CHECK_FOR_FORK();
    __CFRunLoopSourceLock(rls);
    if (__CFIsValid(rls)) {
	__CFRunLoopSourceSetSignaled(rls);
    }
    __CFRunLoopSourceUnlock(rls);
}

外部通过调用CFRunLoopSourceSignal函数,将该source0设置为Signal,然后调用wakeUp唤醒runloop,则该source0任务就可以执行了。在CFSocket.c文件中有这样的代码:

if (shared->_source) {
    CFRunLoopSourceSignal(shared->_source);
    _CFRunLoopSourceWakeUpRunLoops(shared->_source);
}

而_CFRunLoopSourceWakeUpRunLoops就是runloop源码中的内容,其中调用__CFRunLoopSourceWakeUpLoop函数,继而调用CFRunLoopWakeUp函数。

CF_PRIVATE void _CFRunLoopSourceWakeUpRunLoops(CFRunLoopSourceRef rls) {
    CFBagRef loops = NULL;
    __CFRunLoopSourceLock(rls);
    if (__CFIsValid(rls) && NULL != rls->_runLoops) {
        loops = CFBagCreateCopy(kCFAllocatorSystemDefault, rls->_runLoops);
    }
    __CFRunLoopSourceUnlock(rls);
    if (loops) {
	    CFBagApplyFunction(loops, __CFRunLoopSourceWakeUpLoop, NULL);
        CFRelease(loops);
    }
}

static void __CFRunLoopSourceWakeUpLoop(const void *value, void *context) {
    CFRunLoopWakeUp((CFRunLoopRef)value);
}

CFRunLoopWakeUp函数中已经说明了,唤醒(wakeup)操作会放到一个队列中,而队列长度为1。所以,wakeup操作不会重复执行。该操作即调用 __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0) 函数向runloop的_wakeUpPort端口发送一个消息即可,而 __CFSendTrivialMachMessage 函数内部,实际也是调用了mach_msg()函数来实现的。

void CFRunLoopWakeUp(CFRunLoopRef rl) {
    CHECK_FOR_FORK();
    // This lock is crucial to ignorable wakeups, do not remove it.
    __CFRunLoopLock(rl);
    if (__CFRunLoopIsIgnoringWakeUps(rl)) {
        __CFRunLoopUnlock(rl);
        return;
    }
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
    kern_return_t ret;
    /* We unconditionally try to send the message, since we don't want
     * to lose a wakeup, but the send may fail if there is already a
     * wakeup pending, since the queue length is 1. */
    ret = __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0);
    if (ret != MACH_MSG_SUCCESS && ret != MACH_SEND_TIMED_OUT) CRASH("*** Unable to send message to wake up port. (%d) ***", ret);
#elif DEPLOYMENT_TARGET_WINDOWS
    SetEvent(rl->_wakeUpPort);
#endif
    __CFRunLoopUnlock(rl);
}

static uint32_t __CFSendTrivialMachMessage(mach_port_t port, uint32_t msg_id, CFOptionFlags options, uint32_t timeout) {
    kern_return_t result;
    mach_msg_header_t header;
    header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
    header.msgh_size = sizeof(mach_msg_header_t);
    header.msgh_remote_port = port;
    header.msgh_local_port = MACH_PORT_NULL;
    header.msgh_id = msg_id;
    result = mach_msg(&header, MACH_SEND_MSG|options, header.msgh_size, 0, MACH_PORT_NULL, timeout, MACH_PORT_NULL);
    if (result == MACH_SEND_TIMED_OUT) mach_msg_destroy(&header);
    return result;
}

Source0唤醒runloop的操作,最终也是通过mach msg来实现的,mach port端口即为runloop对象的_wakeUpPort。即先设置Source0为Singal,然后调用CFRunLoopWakeUp函数,其中调用mach_msg()函数。这与 __CFRunLoopServiceMachPort 中最终唤醒runloop的方式一致,确实是殊途同归。这显然是合理的,因为唤醒runloop的操作实际上是mach内核的状态切换,即 用户态=>内核态,而mach内核中只能通过mach msg来实现通信。

Source0是如何加到runloop中的

CFRunLoopAddSource函数将runloop的Source加到指定的runloopMode中。

perform函数指针是哪里指定的?info是参数。

void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {	/* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rls)) return;
    Boolean doVer0Callout = false;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {
	    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	    if (NULL == rl->_commonModeItems) {
	        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	    }
	    CFSetAddValue(rl->_commonModeItems, rls);

	    if (NULL != set) {
	        CFTypeRef context[2] = {rl, rls};
	        /* add new item to all common-modes */
	        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
	        CFRelease(set);
	    }
    } else {
	    CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
	    if (NULL != rlm && NULL == rlm->_sources0) {
	        rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	        rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	        rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
	    }
	    if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
	        if (0 == rls->_context.version0.version) {
	            CFSetAddValue(rlm->_sources0, rls);
	        } else if (1 == rls->_context.version0.version) {
	            CFSetAddValue(rlm->_sources1, rls);
		        __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
		        if (CFPORT_NULL != src_port) {
		            CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
		            __CFPortSetInsert(src_port, rlm->_portSet);
	            }
	        }
	        __CFRunLoopSourceLock(rls);
	        if (NULL == rls->_runLoops) {
	            rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
	        }
	        CFBagAddValue(rls->_runLoops, rl);
	        __CFRunLoopSourceUnlock(rls);
	        if (0 == rls->_context.version0.version) {
	            if (NULL != rls->_context.version0.schedule) {
	                doVer0Callout = true;
	            }
	        }
	    }
        if (NULL != rlm) {
	        __CFRunLoopModeUnlock(rlm);
	    }
    }
    __CFRunLoopUnlock(rl);
    if (doVer0Callout) {
        // although it looses some protection for the source, we have no choice but
        // to do this after unlocking the run loop and mode locks, to avoid deadlocks
        // where the source wants to take a lock which is already held in another
        // thread which is itself waiting for a run loop/mode lock
	    rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName);	/* CALLOUT */
    }
}

CFRunLoopAddSource函数将该source添加到runloopMode的mode items中。如果是common mode,则调用 CFSetAddValue(rl->_commonModeItems, rls); ;如果是source0,则调用 CFSetAddValue(rlm->_sources0, rls); ;如果是source1,则调用 ***CFSetAddValue(rlm->_sources1, rls);***,同时还有port的设置。

对于source0,会有一个schedule回调函数,即当source被加入到runloop时触发的回调函数,使用 rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); / CALLOUT / 来调用。

处理Sources1任务 __CFRunLoopDoSource1

sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; 的调用时机是在runloop被唤醒之后。

// msg, size and reply are unused on Windows
static Boolean __CFRunLoopDoSource1() __attribute__((noinline));
static Boolean __CFRunLoopDoSource1(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopSourceRef rls
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                                    , mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply
#endif
                                    ) {	/* DOES CALLOUT */
    CHECK_FOR_FORK();
    Boolean sourceHandled = false;

    /* Fire a version 1 source */
    CFRetain(rls);
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
    __CFRunLoopSourceLock(rls);
    if (__CFIsValid(rls)) {
	__CFRunLoopSourceUnsetSignaled(rls);
	__CFRunLoopSourceUnlock(rls);
        __CFRunLoopDebugInfoForRunLoopSource(rls);
        // 执行source1中设置的perform回调。
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(rls->_context.version1.perform,
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            msg, size, reply,
#endif
            rls->_context.version1.info);
	CHECK_FOR_FORK();
	sourceHandled = true;
    } else {
        if (_LogCFRunLoop) { CFLog(kCFLogLevelDebug, CFSTR("%p (%s) __CFRunLoopDoSource1 rls %p is invalid"), CFRunLoopGetCurrent(), *_CFGetProgname(), rls); }
	__CFRunLoopSourceUnlock(rls);
    }
    CFRelease(rls);
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);
    return sourceHandled;
}

执行任务的代码为:

__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(rls->_context.version1.perform,
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            msg, size, reply,
#endif
            rls->_context.version1.info);

__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__的函数原型如下:

// runloop被唤醒处理事件,处理source1事件。
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info),
        mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply,
#else
        void (*perform)(void *),
#endif
        void *info) {
    if (perform) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        *reply = perform(msg, size, kCFAllocatorSystemDefault, info);
#else
        perform(info);
#endif
    }
    asm __volatile__(""); // thwart tail-call optimization
}

该函数的参数非常多,msg是mach msg对象,reply是mach msg消息通信执行后的回复消息。perform是回调函数指针,包含了四个参数,info也用于向回调函数中传递参数。

Source1是如何唤醒runloop的

之前在分析runloop进入休眠的时候讲过,调用 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm); ,其内部调用mach_msg()函数完成mach内核的状态改变,即使runloop休眠。

Source1是如何加到runloop中的

source1加到runloop中的代码与source0的一致,只是多出了port的设置。

__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if (CFPORT_NULL != src_port) {
    CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
    __CFPortSetInsert(src_port, rlm->_portSet);
}

处理Observer任务 __CFRunLoopDoObservers

__CFRunLoopDoObservers函数将runloop的六种状态通知给Observer。

  1. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); 即将进入runloop
  2. __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); 即将处理timer
  3. __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); 即将处理Source0
  4. __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); 即将进入休眠
  5. __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); 从休眠中被唤醒
  6. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); 即将退出runloop

如果是Source1直接唤醒runloop,则使用goto语句跳转到handle_msg执行,而不会发出AfterWaiting状态的通知!所以AfterWaiting的通知仅仅是针对非Source1的唤醒场景,即手动通过UITouch事件、Timer等唤醒runloop。

/* rl is locked, rlm is locked on entrance and exit */
static void __CFRunLoopDoObservers() __attribute__((noinline));
static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity) {	/* DOES CALLOUT */
    CHECK_FOR_FORK();
获取runloop的mode下的observer数量,最多1024个?
    CFIndex cnt = rlm->_observers ? CFArrayGetCount(rlm->_observers) : 0;
    if (cnt < 1) return;

    /* Fire the observers */
    STACK_BUFFER_DECL(CFRunLoopObserverRef, buffer, (cnt <= 1024) ? cnt : 1);
    CFRunLoopObserverRef *collectedObservers = (cnt <= 1024) ? buffer : (CFRunLoopObserverRef *)malloc(cnt * sizeof(CFRunLoopObserverRef));
    // 遍历runloop的mode下的所有observer,获取CFRunLoopObserverRef对象。 收集有效的observer。
    CFIndex obs_cnt = 0;
    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);
        }
    }
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
    // 对所有observer执行回调
    for (CFIndex idx = 0; idx < obs_cnt; idx++) {
        CFRunLoopObserverRef rlo = collectedObservers[idx];
        __CFRunLoopObserverLock(rlo);
        if (__CFIsValid(rlo)) {
            Boolean doInvalidate = !__CFRunLoopObserverRepeats(rlo);
            __CFRunLoopObserverSetFiring(rlo);
            __CFRunLoopObserverUnlock(rlo);
            // 执行observer的回调函数rlo->_callout,唤醒runloop,将其置为__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__状态。
            __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(rlo->_callout, rlo, activity, rlo->_context.info);
            if (doInvalidate) {
                CFRunLoopObserverInvalidate(rlo);
            }
            __CFRunLoopObserverUnsetFiring(rlo);
        } else {
            __CFRunLoopObserverUnlock(rlo);
        }
        CFRelease(rlo);
    }
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);

    if (collectedObservers != buffer) free(collectedObservers);
}

该函数中对每个有效的Observer,分别执行 CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION(rlo->_callout, rlo, activity, rlo->_context.info); 。该函数接收四个参数,分别是是observer的回调函数func、observer对象自身、runloop状态即activity对象、info对象可以用于传递参数。

static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    if (func) {
        func(observer, activity, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}

Observer是如何加到runloop中的

void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef rlo, CFStringRef modeName) {
    CHECK_FOR_FORK();
    CFRunLoopModeRef rlm;
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rlo) || (NULL != rlo->_runLoop && rlo->_runLoop != rl)) return;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {
	    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	    if (NULL == rl->_commonModeItems) {
	        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	    }
	    CFSetAddValue(rl->_commonModeItems, rlo);
	    if (NULL != set) {
	        CFTypeRef context[2] = {rl, rlo};
	        /* add new item to all common-modes */
	        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
	        CFRelease(set);
	    }
    } else {
	    rlm = __CFRunLoopFindMode(rl, modeName, true);
	    if (NULL != rlm && NULL == rlm->_observers) {
	        rlm->_observers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
	    }
	    if (NULL != rlm && !CFArrayContainsValue(rlm->_observers, CFRangeMake(0, CFArrayGetCount(rlm->_observers)), rlo)) {
            Boolean inserted = false;
            for (CFIndex idx = CFArrayGetCount(rlm->_observers); idx--; ) {
                CFRunLoopObserverRef obs = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
                if (obs->_order <= rlo->_order) {
                    CFArrayInsertValueAtIndex(rlm->_observers, idx + 1, rlo);
                    inserted = true;
                    break;
                }
            }
            if (!inserted) {
	            CFArrayInsertValueAtIndex(rlm->_observers, 0, rlo);
            }
	        rlm->_observerMask |= rlo->_activities;
	        __CFRunLoopObserverSchedule(rlo, rl, rlm);
	    }
        if (NULL != rlm) {
	        __CFRunLoopModeUnlock(rlm);
	    }
    }
    __CFRunLoopUnlock(rl);
}

将Observer加到runloop中的操作基本与timer、source类似,依然会有commonModes的考虑(CFSetAddValue(rl->_commonModeItems, rlo);)。非commonMode下,直接 调用 CFArrayInsertValueAtIndex(rlm->_observers, idx + 1, rlo); 添加到runloopMode的_observers容器中即可。

Obsever和Timer只能被添加到一个runloop中,而Source可以添加到多个runloop中。CFRunLoopTimerRef和CFRunLoopObserverRef中都只有一个 CFRunLoopRef _runLoop; 字段, 而CFRunLoopSourceRef中则有一个 CFMutableBagRef _runLoops; 容器。

主线程相关的一些内容

__CFRunLoopRun函数中有一段main queue相关的代码:

mach_port_name_t dispatchPort = MACH_PORT_NULL;
/// 判断是主线程队列main_queue。
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) ||       
            (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
/// 这里的 dispatchPort 是主线程任务dispatch需要的mach port
// 如果是主线程的runloop,且是CommonModes
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) {
    dispatchPort = _dispatch_get_main_queue_port_4CF();
}

而除此之外,dispatchPort即为MACH_PORT_NULL。即仅在主线程的runloop中,dispatchPort才有值,意味着dispatchPort就是专门用于主线程中的。

dispatchPort、modeQueuePort

其中的dispatchPort即为主线程收发mach msg的端口,端口的另一段应该即是libdispatch了。所以这个port就是与 dispatch_async(dispatch_get_main_queue(), ^{ xxx }) 相关的。

dispatch_async(dispatch_get_main_queue(), ^{ xxx }) 到底是怎么执行任务的?是dispatchPort?还是说如当调用了 dispatch_async(dispatch_get_main_queue(), block)时,主队列会把该 block 放到对应的线程(恰好是主线程)中,主线程的 RunLoop 会被唤醒,从消息中取得这个 block,回调 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 来执行这个 block:

_dispatch_get_main_queue_port_4CF()函数如下:

dispatch_runloop_handle_t
_dispatch_get_main_queue_port_4CF(void)
{
	return _dispatch_get_main_queue_handle_4CF();
}

dispatch_runloop_handle_t
_dispatch_get_main_queue_handle_4CF(void)
{
	dispatch_queue_main_t dq = &_dispatch_main_q;
	dispatch_once_f(&_dispatch_main_q_handle_pred, dq,
			_dispatch_runloop_queue_handle_init);
	return _dispatch_runloop_queue_get_handle(dq->_as_dl);
}

DISPATCH_ALWAYS_INLINE
static inline dispatch_runloop_handle_t
_dispatch_runloop_queue_get_handle(dispatch_lane_t dq)
{
#if TARGET_OS_MAC
	return ((dispatch_runloop_handle_t)(uintptr_t)dq->do_ctxt);
#elif defined(__linux__)
	// decode: 0 is a valid fd, so offset by 1 to distinguish from NULL
	return ((dispatch_runloop_handle_t)(uintptr_t)dq->do_ctxt) - 1;
#else
#error "runloop support not implemented on this platform"
#endif
}

而在MAC平台上,dispatch_runloop_handle_t实际就是mach_port_t。

#if TARGET_OS_MAC
typedef mach_port_t dispatch_runloop_handle_t;
#elif defined(__linux__)
typedef int dispatch_runloop_handle_t;
#else
#error "runloop support not implemented on this platform"
#endif

另外,还有一个modeQueuePort是MACOSX上才有,是当前runloop所在queue的mach_port_name_t对象。

#if TARGET_OS_MAC
dispatch_runloop_handle_t
_dispatch_runloop_root_queue_get_port_4CF(dispatch_queue_t dq)
{
	if (unlikely(dx_type(dq) != DISPATCH_QUEUE_RUNLOOP_TYPE)) {
		DISPATCH_CLIENT_CRASH(dx_type(dq), "Not a runloop queue");
	}
	return _dispatch_runloop_queue_get_handle(upcast(dq)._dl);
}
#endif

__CFRUNLOOP_IS_CALLING与__CFRUNLOOP_IS_SERVICING,这一系列函数在runloop中叫做callout函数。实际代码都很简单,不过函数命名如此引入瞩目也是有道理的,因为他们才是真正做事情的地方。

主线程相关的callout函数

runloop被唤醒后,如果唤醒runloop的livePort就是主线程的dispatchPort,则会执行callout函数 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE

if (livePort == dispatchPort) {
    CFRUNLOOP_WAKEUP_FOR_DISPATCH();
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
    _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
    /// 这里的dispatchPort是否意味着就是主线程?是的!
    /// 如果有dispatch到main queue的block,即执行。
    __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
    _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);
    sourceHandledThisLoop = true;
    didDispatchPortLastTime = true;
}

CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE 是基于mach msg的,其函数原型为:

static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() __attribute__((noinline));
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(void *msg) {
    _dispatch_main_queue_callback_4CF(msg);
    asm __volatile__(""); // thwart tail-call optimization
}

所以,目前的结论就是:主线程的GCD事件的执行过程需要mach msg,这里的msg就是runloop休眠被唤醒时的msg,那实际上是起了什么作用呢?

关于_dispatch_main_queue_callback_4CF()函数,就需要看libdispatch源码了,在queue.c文件中。至于这个函数的内部实现代码,那显然是另外一个话题了。目前仅知道它会调度并执行main queue中的任务即可。

void
_dispatch_main_queue_callback_4CF(
		void *ignored DISPATCH_UNUSED)
{
	// the main queue cannot be suspended and no-one looks at this bit
	// so abuse it to avoid dirtying more memory

	if (_dispatch_main_q.dq_side_suspend_cnt) {
		return;
	}
	_dispatch_main_q.dq_side_suspend_cnt = true;
	_dispatch_main_queue_drain(&_dispatch_main_q);
	_dispatch_main_q.dq_side_suspend_cnt = false;
}

不过,从上边的代码看出来,该mach msg传递到GCD中,好像并未使用到。。。

runloop休眠之前的mach msg

我们注意到,在进入内层循环do-while(1)之前,即runloop即将休眠时,有调用过一次 __CFRunLoopServiceMachPort 函数。

if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if TARGET_OS_MAC
    msg = (mach_msg_header_t *)msg_buffer;
    if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL, rl, rlm)) {
        goto handle_msg;
    }
}

这一步调用__CFRunLoopServiceMachPort,为了保证GCD到main queue的block总是能够尽快保证执行。不过在runloop运行的首次外层循环,或者上次runloop唤醒不是因为GCD主线程,则不会调用。

GCD相关的一些内容

// 通常使用GCD,断点不会出现runloop的callout函数
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"dispatch_get_global_queue");
});

子线程使用GCD,断点不会出现runloop的callout函数。因为这是GCD自行处理的,跟runloop无关。

而主线程则不一样:

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"dispatch_get_main_queue");
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    dispatch_async(dispatch_get_main_queue(), ^{
        /// 这里断点,会出现runloop的callout函数,为啥???
        // 使用GCD异步操作的时候,我们在一个子线程处理完一些事情后,要返回主线程处理事情的时候,这时候需要依赖于RunLoop。内部会调用__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__函数。
        NSLog(@"dispatch_get_main_queue");
    });
});

说明了,主线程的任务执行都是runloop来安排的。

GCD是如何唤醒runloop的

其他可能需要注意的部分

除了以上runloop的主流程外,还有一些可能需要注意的部分。

runloop相关的一些状态设置

runloop的一些自身状态是通过_per_run_data来保存的,如 rl->_perRunData->stoppedrl->_perRunData->ignoreWakeUps ,在前边数据结构章节已经讲过。

另外,还会通过runloop的_cfinfo[CF_INFO_BITS]来查询和设置runloop的一些状态,如isSleeping和isDeallocating。

CF_INLINE Boolean __CFRunLoopIsSleeping(CFRunLoopRef rl) {
    return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 1, 1);
}

CF_INLINE void __CFRunLoopSetSleeping(CFRunLoopRef rl) {
    __CFBitfieldSetValue(((CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 1, 1, 1);
}

CF_INLINE void __CFRunLoopUnsetSleeping(CFRunLoopRef rl) {
    __CFBitfieldSetValue(((CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 1, 1, 0);
}

CF_INLINE Boolean __CFRunLoopIsDeallocating(CFRunLoopRef rl) {
    return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 2, 2);
}

CF_INLINE void __CFRunLoopSetDeallocating(CFRunLoopRef rl) {
    __CFBitfieldSetValue(((CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 2, 2, 1);
}

runloop的source0只包含一个回调函数,而isSignaled用于标记source0是否可以被处理。

/* Bit 1 of the base reserved bits is used for signalled state */

CF_INLINE Boolean __CFRunLoopSourceIsSignaled(CFRunLoopSourceRef rls) {
    return (Boolean)__CFBitfieldGetValue(rls->_bits, 1, 1);
}

CF_INLINE void __CFRunLoopSourceSetSignaled(CFRunLoopSourceRef rls) {
    __CFBitfieldSetValue(rls->_bits, 1, 1, 1);
}

CF_INLINE void __CFRunLoopSourceUnsetSignaled(CFRunLoopSourceRef rls) {
    __CFBitfieldSetValue(rls->_bits, 1, 1, 0);
}

此外,还有observer的isFiring,timer的isFiring等,这里就不一一细说了。

runloop如何切换mode

源码中有很多lock和unlock的操作。

__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);

在__CFRunLoopRun函数中,有三处unlock mode的操作:

  1. runloop即将休眠之前
  2. runloop被唤醒后,处理GCD main的任务时
  3. runloop退出之后

说明,runloop有可能在此时进行runloopMode的切换操作。

runloop的手动唤醒

这里的手动唤醒,指的是调用 CFRunLoopWakeUp 函数。

在前边,关于CFRunLoopWakeUp函数的一些内容,已经做了说明。唤醒(wakeup)操作会放到一个队列中,而队列长度为1。所以,wakeup操作不会重复执行。该操作即调用 __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0) 函数向runloop的_wakeUpPort端口发送一个消息即可,而 __CFSendTrivialMachMessage 函数内部,实际也是调用了mach_msg()函数来实现的。

结论就是runloop的唤醒本质上都是mach_msg函数,即mach内核状态的切换确实是只能通过mach msg来做到。

runloop的超时

runloop的运行时间是通过外部传入的,其内部通过gcd timer或mk timer来确保正确的运行时间。runloop超时的回调函数即为__CFRunLoopTimeout。runloop取消的回调函数为__CFRunLoopTimeoutCancel。

#if __HAS_DISPATCH__
    // 这个即为runloop运行时间的定时器,超时则会调用__CFRunLoopTimeout函数
    dispatch_source_t timeout_timer = NULL;
#endif
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
#if __HAS_DISPATCH__
	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;
#endif
	timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
	timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
#if __HAS_DISPATCH__
	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);
#endif
    } else { // infinite timeout
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }

__timeout_context中会保存runloop对象,还可能有gcd timer对象dispatch_source_t ds。

struct __timeout_context {
#if __HAS_DISPATCH__
    dispatch_source_t ds;
#endif
    CFRunLoopRef rl;
    _Atomic(uint64_t) termTSR;
};

runloop对象是如何销毁的

之前,我们提到:在thread对象销毁的时候,其TSD中的runloop对象也会销毁,调用的就是 __CFFinalizeRunLoop() 函数。

// Called for each thread as it exits
CF_PRIVATE void __CFFinalizeRunLoop(uintptr_t data) {
    CFRunLoopRef rl = NULL;
    if (data <= 1) {
	    __CFLock(&loopsLock);
	    if (__CFRunLoops) {
	        rl = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(pthread_self()));
	        if (rl) CFRetain(rl);
            // 移除__CFRunLoops中的对应runloop对象
	        CFDictionaryRemoveValue(__CFRunLoops, pthreadPointer(pthread_self()));
	    }
	    __CFUnlock(&loopsLock);
    } else {
        // 这里有个递归调用
        _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(data - 1), (void (*)(void *))__CFFinalizeRunLoop);
    }
    if (rl && CFRunLoopGetMain() != rl) { // protect against cooperative threads
        if (NULL != rl->_counterpart) {
            CFRelease(rl->_counterpart);
	        rl->_counterpart = NULL;
        }
	    // purge all sources before deallocation
        CFArrayRef array = CFRunLoopCopyAllModes(rl);
        for (CFIndex idx = CFArrayGetCount(array); idx--;) {
            CFStringRef modeName = (CFStringRef)CFArrayGetValueAtIndex(array, idx);
            // 移除runloopMode中的sources
            __CFRunLoopRemoveAllSources(rl, modeName);
        }
        __CFRunLoopRemoveAllSources(rl, kCFRunLoopCommonModes);
        CFRelease(array);
    }
    // 线程销毁时,将其runloop也销毁。
    if (rl) CFRelease(rl);
}

销毁runloop对象之前,要先将其从__CFRunLoops全局字典中移除,同时遍历其所有的mode,依次移除每个mode中的所有source对象,最后销毁runloop对象。

__CFFinalizeRunLoop()函数的参数data是什么?可以简单理解为runloop对象的引用计数,对应于__CFTSDKeyRunLoopCntr这个slot。data > 1时,只会将__CFTSDKeyRunLoopCntr对应的值执行减1操作;而 data <= 1 时,才会真正执行runloop的销毁代码。

关于__CFTSDKeyRunLoopCntr这个slot,有三处调用:

  1. _CFRunLoopGet0()函数中:***_CFSetTSD(__CFTSDKeyRunLoopCntr, (void )(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void ()(void ))__CFFinalizeRunLoop);**
  2. __CFFinalizeRunLoop()函数中:***_CFSetTSD(__CFTSDKeyRunLoopCntr, (void )(data - 1), (void ()(void ))__CFFinalizeRunLoop);**
  3. _CFRunLoopSetCurrent()函数中:**_CFSetTSD(__CFTSDKeyRunLoopCntr, 0, (void ()(void ))__CFFinalizeRunLoop);**

runloopMode对象销毁时,也会对应释放其mode item等对象:

static void __CFRunLoopModeDeallocate(CFTypeRef cf) {
    CFRunLoopModeRef rlm = (CFRunLoopModeRef)cf;
    if (NULL != rlm->_sources0) CFRelease(rlm->_sources0);
    if (NULL != rlm->_sources1) CFRelease(rlm->_sources1);
    if (NULL != rlm->_observers) CFRelease(rlm->_observers);
    if (NULL != rlm->_timers) CFRelease(rlm->_timers);
    if (NULL != rlm->_portToV1SourceMap) CFRelease(rlm->_portToV1SourceMap);
    CFRelease(rlm->_name);
    __CFPortSetFree(rlm->_portSet);
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    if (rlm->_timerSource) {
        dispatch_source_cancel(rlm->_timerSource);
        dispatch_release(rlm->_timerSource);
    }
    if (rlm->_queue) {
        dispatch_release(rlm->_queue);
    }
#endif
#if USE_MK_TIMER_TOO
    if (MACH_PORT_NULL != rlm->_timerPort) mk_timer_destroy(rlm->_timerPort);
#endif
    pthread_mutex_destroy(&rlm->_lock);
    memset((char *)cf + sizeof(CFRuntimeBase), 0x7C, sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase));
}

一个特殊的变量poll

在每次runloop的一次run开始时,__CFRunLoopDoSources0执行后会有这个变量poll。

Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

关于这个poll的判断,有些奇怪的地方,暂时不懂为何这样做。

每次 loop 如果处理了 source0 任务,那么 poll 值会为 true,直接的影响是不会 DoObservers-BeforeWaiting 和 DoObservers-AfterWaiting,也就是说 runloop 会直接进入睡眠,而且不会告知 BeforeWaiting 和 AfterWaiting 这两个 activity。所以你看,有些情况下,可能 runloop 经过了几个 loop,但你注册的 observer 却不会收到 callback。

参考资料

  1. Event loop
  2. Run Loops
  3. NSRunLoop
  4. CFRunLoop
  5. CFRunLoopPerformBlock
  6. CFRunLoopPerformBlock vs dispatch_async
  7. RunLoop.subproj
  8. Kernel Programming Guide: Mach Overview
  9. mach_msg
  10. CFPlatform.c
  11. AsyncDisplayKit,已更名为Texture
  12. 深入理解RunLoop
  13. 解密 Runloop
  14. iOS刨根问底-深入理解RunLoop
  15. 优化UITableViewCell高度计算的那些事
  16. 重拾RunLoop原理
  17. iOS RunLoop详解