RunLoop源码学习(三)核心逻辑

898 阅读21分钟

2021-08-15

前言

本文学习探讨 RunLoop 的核心逻辑。RunLoop 的核心逻辑是对 Sources、Observers 和 Timers 的响应逻辑。本篇在进入第五章介绍CFRunLoopRun之前,均暂不考虑事件触发时间点的问题,只考虑进入响应流程后的事件处理流程。CFRunLoopRun实际上是对前四章的 DoBlocks、DoObservers、DoSources、DoTimers 操作的组织编排。

一、DoBlocks

RunLoop 对 blocks 链表的处理是按续处理的,处理过程中需要考虑到,在 blocks 链表遍历执行过程中,RunLoop 可能会挂载新的 block。RunLoop 的处理策略是,进入 blocks 处理流程时,将 blocks 链表捞出来(用一个局部变量指针指向旧链表头部),然后清空 RunLoop 的 blocks 链表指针。处理旧链表时,如果 block 被执行则将 block 从链表中移除。在 blocks 处理完成后,将处理过程中新挂载的 blocks 拼接在处理完成的旧链表尾部。

处理过程可以参考下图。其中,绿色是旧的不需要执行的 block 节点,红色是旧的需要执行的 block 节点,黄色是 blocks 处理过程中新挂载的 blocks 节点。

loadimage.png

static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
    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;
    // NOTE: 注意这里把链表捞出来后把rl的blocks链表置NULL了,也就是说后面的block处理过程中,
    // block链表也可以持续挂载元素
    rl->_blocks_head = NULL;
    rl->_blocks_tail = NULL;
    CFSetRef commonModes = rl->_commonModes;
    CFStringRef curMode = rlm->_name;
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
    struct _block_item *prev = NULL;
    struct _block_item *item = head;
    // 核心操作1:遍历blocks链表的所有节点
    while (item) {
        struct _block_item *curr = item;
        item = item->_next;
	Boolean doit = false;
        // 核心操作2:blocks可能是Mode类型也可能是set类型,所有采用两种处理方式。判断标准是:
        // 如果当前Mode和block的_mode相等,或者_mode是CommonModes之一
	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) {
            // 核心操作3:执行block并从链表中移除节点
	    if (prev) prev->_next = item;
	    if (curr == head) head = item;
	    if (curr == tail) tail = prev;
	    void (^block)(void) = curr->_block;
            CFRelease(curr->_mode);
            free(curr);
	    if (doit) {
                __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);
    // NOTE:注意如果旧的blocks链表处理完成后,仍存在未执行的block,则将rl的新blocks链表
    //(旧blocks链表处理过程中rl新挂载的block),挂在旧blocks链表尾部,并更新rl的blocks
    // 链表修改为旧的blocks。也就是说blocks
    if (head) {
	tail->_next = rl->_blocks_head;
	rl->_blocks_head = head;
        if (!rl->_blocks_tail) rl->_blocks_tail = tail;
    }
    return did;
}

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
}

二、DoObservers

对观察者的处理比较简单:捞出所有目标 RunLoopMode 下需要处理的所有 Observers,遍历并触发 Observers 的回调。判断需要处理的标准是:

  • RunLoop 的状态activity对应得上;
  • Observer 当前有效(Observer 在 Invalidate 后无效);
  • Observer 当前不是正在处理中;

注意,需要移除 Observer 时(指定了 Observer 无需重复触发),需要遍历 RunLoop 的 commonModeItems 以移除目标 Observer 对应的项,且遍历 RunLoop 的所有 Mode,以移除所有对目标 Observer 的引用。

NOTE:由于是先捞出 Observer 的引用保存到 collectedObservers 中处理,所以for迭代过程中一边移除 RunLoop 的 Observer 并不会引发异常。

static void __CFRunLoopDoObservers() __attribute__((noinline));
static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity) {	/* DOES CALLOUT */
    CHECK_FOR_FORK();

    CFIndex cnt = rlm->_observers ? CFArrayGetCount(rlm->_observers) : 0;
    if (cnt < 1) return;

    // 核心操作1:捞出所有需要处理的Observers,填入缓冲数组collectedObservers中
    STACK_BUFFER_DECL(CFRunLoopObserverRef, buffer, (cnt <= 1024) ? cnt : 1);
    CFRunLoopObserverRef *collectedObservers = (cnt <= 1024) ? buffer : (CFRunLoopObserverRef *)malloc(cnt * sizeof(CFRunLoopObserverRef));
    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);
    // 核心操作2:遍历缓冲数组collectedObservers,并触发Observer的回调且必要时Invalidate
    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);
            __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);
}

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
}

void CFRunLoopObserverInvalidate(CFRunLoopObserverRef rlo) {    /* DOES CALLOUT */
    CHECK_FOR_FORK();
    __CFGenericValidateType(rlo, CFRunLoopObserverGetTypeID());
    __CFRunLoopObserverLock(rlo);
    CFRetain(rlo);
    if (__CFIsValid(rlo)) {
        CFRunLoopRef rl = rlo->_runLoop;
        void *info = rlo->_context.info;
        rlo->_context.info = NULL;
        __CFUnsetValid(rlo);
        if (NULL != rl) {
            CFRetain(rl);
            __CFRunLoopObserverUnlock(rlo);
            __CFRunLoopLock(rl);
            // 核心操作1:移除所有Mode中的目标Observer
            CFArrayRef array = CFRunLoopCopyAllModes(rl);
            for (CFIndex idx = CFArrayGetCount(array); idx--;) {
                CFStringRef modeName = (CFStringRef)CFArrayGetValueAtIndex(array, idx);
                CFRunLoopRemoveObserver(rl, rlo, modeName);
            }
            // 核心操作2:移除commonModes中目标Observer对应的commonModeItem
            CFRunLoopRemoveObserver(rl, rlo, kCFRunLoopCommonModes);
            __CFRunLoopUnlock(rl);
            CFRelease(array);
            CFRelease(rl);
            __CFRunLoopObserverLock(rlo);
        }
        if (NULL != rlo->_context.release) {
            rlo->_context.release(info);        /* CALLOUT */
        }
    }
    __CFRunLoopObserverUnlock(rlo);
    CFRelease(rlo);
}

void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef rlo, CFStringRef modeName) {
    CHECK_FOR_FORK();
    CFRunLoopModeRef rlm;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {
        // 核心操作1:移除commonModes中目标Observer对应的commonModeItem
	if (NULL != rl->_commonModeItems && CFSetContainsValue(rl->_commonModeItems, rlo)) {
	    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	    CFSetRemoveValue(rl->_commonModeItems, rlo);
	    if (NULL != set) {
		CFTypeRef context[2] = {rl, rlo};
		CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context);
		CFRelease(set);
	    }
	}
    } else {
        // 核心操作2:移除mode的observers中的目标Observer
	rlm = __CFRunLoopFindMode(rl, modeName, false);
	if (NULL != rlm && NULL != rlm->_observers) {
	    CFRetain(rlo);
            CFIndex idx = CFArrayGetFirstIndexOfValue(rlm->_observers, CFRangeMake(0, CFArrayGetCount(rlm->_observers)), rlo);
            if (kCFNotFound != idx) {
                CFArrayRemoveValueAtIndex(rlm->_observers, idx);
	        __CFRunLoopObserverCancel(rlo, rl, rlm);
            }
	    CFRelease(rlo);
	}
        if (NULL != rlm) {
	    __CFRunLoopModeUnlock(rlm);
	}
    }
    __CFRunLoopUnlock(rl);
}

NOTE:Observer 和 Sources 共用了一套有效位管理方法,也就是上面的__CFIsValid__CFSetValid__CFUnsetValid方法。以上三个方法的实现是基于_base成员,而 Observer 和 Sources 具备相同类型的_base,所以才行得通。

三、DoSources

对 Sources 的处理,Source0 和 Source1 的逻辑是独立。

3.1 DoSource0

对 Sources0 的处理函数如下。第一步还是先捞出rlm中的所有 Sources0 的引用并保存到局部变量sources中,CFSetApplyFunction处理完成,输出参数sources可能是NULL或者CFRunLoopSource或者CFSetCFRunLoopSource的集合),所以才会先根据CFGetTypeID(sources) == CFRunLoopSourceGetTypeID()获取sources的具体类型区别处理。两个分支其实有不少基本相同的代码,区别是else分支(sources类型为CFSet时)需要判断,如果已处理某个 Source0,且指定处理完一个 Source 后需要立即返回(stopAfterHandle),则立即跳出循环。

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;

    // 核心操作1:捞出Mode中所有的Source0的引用保存到sources局部变量
    if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) {
	CFSetApplyFunction(rlm->_sources0, (__CFRunLoopCollectSources0), &sources);
    }
    if (NULL != sources) {
	__CFRunLoopModeUnlock(rlm);
	__CFRunLoopUnlock(rl);
	if (CFGetTypeID(sources) == CFRunLoopSourceGetTypeID()) {
	    CFRunLoopSourceRef rls = (CFRunLoopSourceRef)sources;
	    __CFRunLoopSourceLock(rls);
            // 核心操作2:判断source0收到signal信号,且source0有效,则触发Source0的回调(if-else)
            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);
            }
	} 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);
                
                // NOTE:这里省略一段与“核心操作2”一模一样的代码
                ...
                
		if (stopAfterHandle && sourceHandled) {
		    break;
		}
	    }
	}
	CFRelease(sources);
	__CFRunLoopLock(rl);
	__CFRunLoopModeLock(rlm);
    }
    return sourceHandled;
}

static void __CFRunLoopCollectSources0(const void *value, void *context) {
    CFRunLoopSourceRef rls = (CFRunLoopSourceRef)value;
    CFTypeRef *sources = (CFTypeRef *)context;
    if (0 == rls->_context.version0.version && __CFIsValid(rls) && __CFRunLoopSourceIsSignaled(rls)) {
	if (NULL == *sources) {
	    *sources = CFRetain(rls);
	} else if (CFGetTypeID(*sources) == CFRunLoopSourceGetTypeID()) {
	    CFTypeRef oldrls = *sources;
	    *sources = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
	    CFArrayAppendValue((CFMutableArrayRef)*sources, oldrls);
	    CFArrayAppendValue((CFMutableArrayRef)*sources, rls);
	    CFRelease(oldrls);
	} else {
	    CFArrayAppendValue((CFMutableArrayRef)*sources, rls);
	}
    }
}

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
}

3.2 DoSource1

对 Sources1 的处理函数如下,省略了对平台类型的预编译判断。从代码看,调用 DoSource1 时,需要指定具体的CFRunLoopSource对象(DoSource0 只需要指定到 Mode),只要CFRunLoopSource是有效的,则立即触发回调。所以 DoSource1 其实是断言了传入的CFRunLoopSource的 version 必定为 1,且已接收到信号(signaled)。

NOTE:DoSource1 只是看起来比较简单,很多判断和处理逻辑放到了 RunLoopRun 过程中。

static Boolean __CFRunLoopDoSource1() __attribute__((noinline));
static Boolean __CFRunLoopDoSource1(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopSourceRef rls, mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply) {	/* DOES CALLOUT */
    CHECK_FOR_FORK();
    Boolean sourceHandled = false;

    CFRetain(rls);
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
    __CFRunLoopSourceLock(rls);
    if (__CFIsValid(rls)) {
	__CFRunLoopSourceUnsetSignaled(rls);
	__CFRunLoopSourceUnlock(rls);
        __CFRunLoopDebugInfoForRunLoopSource(rls);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(rls->_context.version1.perform, msg, size, reply, 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;
}

static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(
        void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info),
        mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply, void *info) {
    if (perform) {
        *reply = perform(msg, size, kCFAllocatorSystemDefault, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}

四、DoTimers

前面 DoBlocks、DoObservers、DoSources 貌似都很简单,不过接下来 DoTimers 处理逻辑就稍微比较硬核了。进入正题前先准备一些基础知识。

4.1 Timer构建

Timer 构建逻辑稍微比较有干货。其中now1CFAbsoluteTimeGetCurrent返回的当前标准时间戳,now2mach_absolute_time返回的 CPU 时钟当前时间戳,两者的基准显然是不相同的,需要通过公式now2 + __CFTimeIntervalToTSR(fireDate - now1)fireData传参转化为 CPU 时钟时间戳。所以代码中xxxFireDate的基准是标准时间戳,而xxxTSR的基准是 CPU 时钟时间戳。具体地,CFRunLoopTimer_nextFireDate_fireTSR其实是表示相同的时间点(定时器下次触发的时间点),只是用了不同的基准。

通常情况下,在 Timer 构建阶段,_fireTSR会比mach_absolute_time()大,也就是说通常会指定 Timer 在未来的某个时间点触发。那么基本可以预测 Timer 的触发判断标准大致就是_fireTSR <= mach_absolute_time()

CFRunLoopTimerRef CFRunLoopTimerCreate(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, CFRunLoopTimerCallBack callout, CFRunLoopTimerContext *context) {
    CHECK_FOR_FORK();
    if (isnan(interval)) {
        CRSetCrashLogMessage("NaN was used as an interval for a CFRunLoopTimer");
        HALT;
    }
    CFRunLoopTimerRef memory;
    UInt32 size;
    size = sizeof(struct __CFRunLoopTimer) - sizeof(CFRuntimeBase);
    memory = (CFRunLoopTimerRef)_CFRuntimeCreateInstance(allocator, CFRunLoopTimerGetTypeID(), size, NULL);
    if (NULL == memory) {
	return NULL;
    }
    __CFSetValid(memory);
    __CFRunLoopTimerUnsetFiring(memory);
    __CFRunLoopLockInit(&memory->_lock);
    memory->_runLoop = NULL;
    memory->_rlModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    memory->_order = order;
    if (interval < 0.0) interval = 0.0;
    memory->_interval = interval;
    memory->_tolerance = 0.0;
    if (TIMER_DATE_LIMIT < fireDate) fireDate = TIMER_DATE_LIMIT;
    memory->_nextFireDate = fireDate;
    memory->_fireTSR = 0ULL;
    uint64_t now2 = mach_absolute_time();
    CFAbsoluteTime now1 = CFAbsoluteTimeGetCurrent();
    if (fireDate < now1) {
	memory->_fireTSR = now2;
    } else if (TIMER_INTERVAL_LIMIT < fireDate - now1) {
	memory->_fireTSR = now2 + __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
    } else {
	memory->_fireTSR = now2 + __CFTimeIntervalToTSR(fireDate - now1);
    }
    memory->_callout = callout;
    if (NULL != context) {
	if (context->retain) {
	    memory->_context.info = (void *)context->retain(context->info);
	} else {
	    memory->_context.info = context->info;
	}
	memory->_context.retain = context->retain;
	memory->_context.release = context->release;
	memory->_context.copyDescription = context->copyDescription;
    } else {
	memory->_context.info = 0;
	memory->_context.retain = 0;
	memory->_context.release = 0;
	memory->_context.copyDescription = 0;
    }
    return memory;
}

4.2 Timer处理

开头的套路是一样的,先捞出目标 RunLoopMode 中需要处理的 Timers,判断是否需要处理的标准是:

  • Timer 有效;
  • Timer 并不是处在正在处理的状态;
  • Timer 的下次触发时间小于等于传入参数limitTSR(通常传mach_absolute_time());

然后,遍历 Timers 调用__CFRunLoopDoTimer函数处理定时任务。

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);
        
        // 核心逻辑1:Timer触发的判断标准
        if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
            if (rlt->_fireTSR <= limitTSR) {
                if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                CFArrayAppendValue(timers, rlt);
            }
        }
    }
    
    // 核心逻辑2:遍历捞出的需要触发的Timers逐个处理
    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;
}

进入正题,看看 RunLoop 在运行中是如何触发及管理 Timer 的。代码比较长,核心操作都标注在代码中。需要注意的是两个操作:

  • ARM mk_timer:在 iOS 平台下 RunLoop 是通过 XNU 内核的mk_timer实现 Timer 事件唤醒(通过 mach port),MAC 平台下则同时使用dispatch_timermk_timer
  • Timer 排序:当 RunLoop 中某个 Timer 的_fireDate发生变更时,都需要调整 Mode 中的_timers数组的排列。排列是为了保证_timers始终按_fireDate升序;

为什么需要保证 Timer 的升序呢?因为 Timers 本身就具有隐性的优先级——触发时间点。从__CFRunLoopDoTimers可以看到,处理 Mode 中的 Timers 时是简单地从头到尾遍历的。试想如果 Timer 是无序的,当若干个 Timers 的回调事件因某种原因延迟触发时(例如 切 Mode、长操作阻塞等等),__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__的执行顺序就会被打乱,反观_timers始终按_fireDate升序则可以保证执行序。

下图应该有助于理解_timers需要按_fireDate升序排列的原因。关注到情况2的由于 Timer1 回调太耗时,而延迟并堆积到一起执行的红色标注的 Timer2、Timer3、Timer4,图中由于_timers是按升序排列所以执行序是正确的 Timer2 >> Timer3 >> Timer4。如果没有升序排列,例如_timers的排列是 (4, 1, 2, 3),则最后堆积到一起回调的顺序则会是 Timer4 >> Timer2 >> Timer3,定时任务间的相对顺序被打乱。

loadimage (2).png

NOTE:本文不包含 Mac OS 平台下使用dispatch_timer实现计时的内容。

// for conservative arithmetic safety, such that (TIMER_DATE_LIMIT + TIMER_INTERVAL_LIMIT + kCFAbsoluteTimeIntervalSince1970) * 10^9 < 2^63
#define TIMER_DATE_LIMIT	4039289856.0
#define TIMER_INTERVAL_LIMIT	504911232.0

static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) {	/* DOES CALLOUT */
    Boolean timerHandled = false;
    uint64_t oldFireTSR = 0;

    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;
        }
        // 核心操作1:指定doInvalidate为true时,定时器触发完成需要执行invalidate,若Timer
        // 的interval为0,表示只需触发一次则doInvalidate置为true
        Boolean doInvalidate = (0.0 == rlt->_interval);
        // NOTE: 关键节点(Timer触发开始)
	__CFRunLoopTimerSetFiring(rlt);
        
        rlm->_timerSoftDeadline = UINT64_MAX;
        rlm->_timerHardDeadline = UINT64_MAX;
        __CFRunLoopTimerUnlock(rlt);
	__CFRunLoopTimerFireTSRLock();
	oldFireTSR = rlt->_fireTSR;
	__CFRunLoopTimerFireTSRUnlock();

        // 核心操作2:schedule下一个Timer触发事件(通过XNU内核mk_timer通过mach_port实现)
        __CFArmNextTimerInMode(rlm, rl);

	__CFRunLoopModeUnlock(rlm);
	__CFRunLoopUnlock(rl);
        // 核心操作3:触发Timer的回调函数
	__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
	CHECK_FOR_FORK();
        // 核心操作4:必要时invalidate定时器
        if (doInvalidate) {
            CFRunLoopTimerInvalidate(rlt);      /* DOES CALLOUT */
        }
        if (context_release) {
            context_release(context_info);
        }
	__CFRunLoopLock(rl);
	__CFRunLoopModeLock(rlm);
        __CFRunLoopTimerLock(rlt);
	timerHandled = true;
        // NOTE: 关键节点(Timer触发结束)
	__CFRunLoopTimerUnsetFiring(rlt);
    }
    // 核心操作5:如果已经处理了Timer,但是Timer仍然没invalidated,则表示Timer具备interval
    // 需要处理,需要计算下一次触发时间
    if (__CFIsValid(rlt) && timerHandled) {
        if (oldFireTSR < rlt->_fireTSR) {
            // NOTE: 为了支持在Timer的回调中调用CFRunLoopTimerSetNextFireDate手动修改
            // Timer的触发时间,如果修改后该Timer仍然是_fireTSR最小,由于此时Timer处在
            // Firing状态,所以SetFireDate操作内部调用Reposition触发ARM操作时,会因为
            // Timer处在Firing被跳过(参考上面NOTE关键节点注释),所以才在这里重新ARM一次
            __CFRunLoopTimerUnlock(rlt);
            __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 {
                // 核心操作6:将基于标准时间的interval转化为TSR
        	intervalTSR = __CFTimeIntervalToTSR(rlt->_interval);
            }
            if (LLONG_MAX - intervalTSR <= oldFireTSR) {
                nextFireTSR = LLONG_MAX;
            } else {
                if (intervalTSR == 0) {
                    CRSetCrashLogMessage("A CFRunLoopTimer with an interval of 0 is set to repeat");
                    HALT;
                }
                uint64_t currentTSR = mach_absolute_time();
                nextFireTSR = oldFireTSR;
                // 核心操作7:循环迭代查找下一个触发时间点(关键)
                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);
                // 核心操作8:捞出Timer绑定的RunLoop的所有Modes
		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();
                // 核心操作9:将上面计算出来的“下次触发时间点”赋值给_fireTSR并更新
                // _nextFireDate(将_fireTSR转化为标准时间戳基准)
		rlt->_fireTSR = nextFireTSR;
                rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
                // 核心操作10:对Timer绑定的RunLoop的所有Modes中的所有Timer进行重新排序,
                // 之所以要操作所有Mode,是因为Timer可以同时与单个RunLoop的多个Mode绑定
		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 {
                //ELSE_BRANCH_OF:if (rlt_rl)
		__CFRunLoopTimerUnlock(rlt);
		__CFRunLoopTimerFireTSRLock();
                // NOTE: 做的是上面核心操作9所做的事情
		rlt->_fireTSR = nextFireTSR;
                rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
		__CFRunLoopTimerFireTSRUnlock();
            }
        }
    } else {
        //ELSE_BRANCH_OF:if (__CFIsValid(rlt) && rlt->_fireTSR <= mach_absolute_time() && !__CFRunLoopTimerIsFiring(rlt) && rlt->_runLoop == rl)
        __CFRunLoopTimerUnlock(rlt);
    }
    CFRelease(rlt);
    return timerHandled;
}

Timer 排序的代码如下。排序使用的是插入排序+二分查找。注意这里的二分查找和通用的二分查找不太一样,代码中flsl函数的规律如下,也就是说这里的二分找到分割点索引(1 << flsl(cnt)) * 2增量是 2 的 n 次幂,例如包含 10 个元素的数组找到的第一个分割点是 8,以此类推。

flsl(0) = 0
flsl(1) = 1
flsl(2) = 2
flsl(4) = 3
flsl(8) = 4
flsl(16) = 5
flsl(32) = 6
...
static void __CFRepositionTimerInMode(CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt, Boolean isInArray) __attribute__((noinline));
static void __CFRepositionTimerInMode(CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt, Boolean isInArray) {
    if (!rlt) return;
    
    CFMutableArrayRef timerArray = rlm->_timers;
    if (!timerArray) return;
    Boolean found = false;
    
    // If we know in advance that the timer is not in the array (just being added now) then we can skip this search
    if (isInArray) {
        CFIndex idx = CFArrayGetFirstIndexOfValue(timerArray, CFRangeMake(0, CFArrayGetCount(timerArray)), rlt);
        if (kCFNotFound != idx) {
            CFRetain(rlt);
            CFArrayRemoveValueAtIndex(timerArray, idx);
            found = true;
        }
    }
    if (!found && isInArray) return;
    CFIndex newIdx = __CFRunLoopInsertionIndexInTimerArray(timerArray, rlt);
    CFArrayInsertValueAtIndex(timerArray, newIdx, rlt);
    __CFArmNextTimerInMode(rlm, rlt->_runLoop);
    if (isInArray) CFRelease(rlt);
}

static CFIndex __CFRunLoopInsertionIndexInTimerArray(CFArrayRef array, CFRunLoopTimerRef rlt) __attribute__((noinline));
static CFIndex __CFRunLoopInsertionIndexInTimerArray(CFArrayRef array, CFRunLoopTimerRef rlt) {
    CFIndex cnt = CFArrayGetCount(array);
    if (cnt <= 0) {
        return 0;
    }
    if (256 < cnt) {
        CFRunLoopTimerRef item = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(array, cnt - 1);
        if (item->_fireTSR <= rlt->_fireTSR) {
            return cnt;
        }
        item = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(array, 0);
        if (rlt->_fireTSR < item->_fireTSR) {
            return 0;
        }
    }

    CFIndex add = (1 << flsl(cnt)) * 2;
    CFIndex idx = 0;
    Boolean lastTestLEQ;
    do {
        add = add / 2;
	lastTestLEQ = false;
        CFIndex testIdx = idx + add;
        if (testIdx < cnt) {
            CFRunLoopTimerRef item = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(array, testIdx);
            if (item->_fireTSR <= rlt->_fireTSR) {
                idx = testIdx;
		lastTestLEQ = true;
            }
        }
    } while (0 < add);

    return lastTestLEQ ? idx + 1 : idx;
}

最后是 ARM mk_timer 的代码,忽略一些平台相关的代码,理解下面的代码需要先理解两个概念:

  • softDeadLine:对计时器的某次计时事件,当时间超过其 softDeadLine 后可触发;
  • hardDeadLine:对计时器的某次计时事件,当时间超过其 hardDeadLine 后则视为超出范围,不可触发;

在不考虑极端情况的前提下,两者之间有固定的关系:hardDeadLine = softDeadLine + tolerance。当平台使用 mk_timer 实现及时时,hardDeadLine 和 tolerance 基本没起到作用。

NOTE:对 iOS 平台而言,tolerance 基本没起到什么作用;对 Mac OS 而言,由于同时使用了 GCD Timer 协同实现计时,则可能在指定leeway参数上会起到一定的指导作用。_hardDeadLine的存在意义则和 _nextFireDate一样,仅在内部起到备注信息的作用,其实并没啥大用。

static void __CFArmNextTimerInMode(CFRunLoopModeRef rlm, CFRunLoopRef rl) {    
    uint64_t nextHardDeadline = UINT64_MAX;
    uint64_t nextSoftDeadline = UINT64_MAX;

    if (rlm->_timers) {
            if (__CFRunLoopTimerIsFiring(t)) continue;
            
            int32_t err = CHECKINT_NO_ERROR;
            // 核心代码1:计算当前所遍历到的计时器的softDeadLine和hardDeadLine
            uint64_t oneTimerSoftDeadline = t->_fireTSR;
            uint64_t oneTimerHardDeadline = check_uint64_add(t->_fireTSR, __CFTimeIntervalToTSR(t->_tolerance), &err);
            if (err != CHECKINT_NO_ERROR) oneTimerHardDeadline = UINT64_MAX;
            
            if (oneTimerSoftDeadline > nextHardDeadline) {
                break;
            }
            
            // 核心代码2:通过遍历Mode的所有计时器,获取最小softDeadLine和hardDeadLine,获得下一
            // 次计时事件的softDeadLine和hardDeadLine
            if (oneTimerSoftDeadline < nextSoftDeadline) {
                nextSoftDeadline = oneTimerSoftDeadline;
            }
            
            if (oneTimerHardDeadline < nextHardDeadline) {
                nextHardDeadline = oneTimerHardDeadline;
            }
        }
        
        if (nextSoftDeadline < UINT64_MAX && (nextHardDeadline != rlm->_timerHardDeadline || nextSoftDeadline != rlm->_timerSoftDeadline)) {
            if (CFRUNLOOP_NEXT_TIMER_ARMED_ENABLED()) {
                CFRUNLOOP_NEXT_TIMER_ARMED((unsigned long)(nextSoftDeadline - mach_absolute_time()));
            }
            
            ...
            
            if (rlm->_timerPort) {
                mk_timer_arm(rlm->_timerPort, __CFUInt64ToAbsoluteTime(nextSoftDeadline));
            }
        } else if (nextSoftDeadline == UINT64_MAX) {
            if (rlm->_mkTimerArmed && rlm->_timerPort) {
                AbsoluteTime dummy;
                mk_timer_cancel(rlm->_timerPort, &dummy);
                rlm->_mkTimerArmed = false;
            }
            
            ...
        }
    }
    rlm->_timerHardDeadline = nextHardDeadline;
    rlm->_timerSoftDeadline = nextSoftDeadline;
}

五、RunLoopRun

关于执行 RunLoop,CFRunLoop公开了两个接口:

  • CFRunLoopRun:Runs the current thread’s CFRunLoop object in its default mode indefinitely
  • CFRunLoopRunInMode:Runs the current thread’s CFRunLoop object in a particular mode

两者的主要区别已经用粗体字标出。CFRunLoopRun是拉起一个无限循环,CFRunLoopRunInMode是指定 RunLoop 在特定的 Mode 上运行。两者的源代码如下,正好印证了以上区别:

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,前者是循环反复调用,后者只调用一次。

5.1 CFRunLoopRunSpecific

CFRunLoopRunSpecific很重要,它既是 RunLoop 入口也是 RunLoop 的出口。下面源代码中,关注到标注出来的核心代码,发现 Observer 的 Entry 事件和 Exit 事件都是在这里触发的,而两个事件所包围的代码是__CFRunLoopRun调用代码,__CFRunLoopRun是 RunLoop 最核心的逻辑。

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        // 奇怪的代码-_-!
	Boolean did = false;
	if (currentMode) __CFRunLoopModeUnlock(currentMode);
	__CFRunLoopUnlock(rl);
	return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    //NOTE: 备份旧 per run data,备份旧 Mode,切换新 Mode
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    // 核心代码:1、运行前触发 Entry 事件;2、在指定Mode上运行;3、完成后触发 Exit 事件;
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    //NOTE: 恢复旧 per run data,切回旧 Mode
    __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

从上述代码发现,RunLoop 的执行和函数调用的行为模式十分类似:

  • 调用函数前,先记录调用前的上下文;
  • 调用函数;
  • 函数返回后,恢复调用前的上下文;

而 RunLoop 则是:

  • 切换 Mode 执行前,先记录旧的 per run data、Mode;
  • 切换 Mode 执行;
  • Mode 执行完成后,恢复旧的 per run data、Mode;

举个具体的例子。下图表示某个包含 3 个 Mode(DefaultMode、ModeA、ModeB)的 RunLoop 的生命周期中,所经历的 Mode 切换过程:

  • 调用CFRunLoopRun,RunLoop 开始在 DefaultMode 执行,触发第一个 Extry 事件;
  • 在 DefaultMode 的某个 Source 的回调中,调用CFRunLoopRunInMode执行 RunLoop 在 ModeA 上运行,此时触发第二个 Entry 事件;
  • 在 ModeA 的某个 Timer 的回调中,调用了CFRunLoopRunInMode执行 RunLoop 在 ModeB 上运行,此时触发第三个 Entry 事件;
  • 在 ModeB 上处理完所有任务后,触发第一个 Exit 事件,回到 ModeA 下继续处理;
  • 在 ModeA 上处理完所有任务后,触发第二个 Exit 事件,回到 DefaultMode 下继续处理;
  • 在 DefaultMode 上处理完所有任务后,RunLoop 结束,触发第三个 Exit 事件;

loadimage.png

NOTE: 从上述 Mode 的切换过程可以发现,频繁在各种监控对象的回调中切换 Mode 时,RunLoop 的线程的调用栈会不断加深,就像函数递归会存在栈溢出的风险,且出现互斥锁锁死的风险也会增加。

5.2 核心中的核心

接下来是最硬核的部分。假设针对 iOS 平台编译,删除__CFRunLoopRun中其他平台相关的代码,得到的完整的源代码如下,其中最重要的代码是__CFRunLoopServiceMachPort,该函数是操作 RunLoop 进入休眠的关键,通过设置 mach port 接收消息实现。设置 mach port 接收消息后,线程会暂时挂起进入休眠,等待 mach port 响应。

从代码中发现,RunLoop 虽然包含了 Source0、Source1、Timers 各种监控对象,但实际上,所有这些监控对象的事件触发(唤醒 RunLoop 去响应事件)都是基于 mach port 实现的:

  • Source0:通过 RunLoop 的_wakeUpPort唤醒 RunLoop;
  • Source1:通过 Source1 绑定的 mach port(Mode 的_portToV1SourceMap)唤醒 RunLoop;
  • Timers:通过 Mode 的_timerPort在恰当的时间点唤醒 RunLoop;

另外,RunLoop 对 GCD 主队列上执行的操作有“特殊照顾”。代码中dispatchPort实现的大致效果是:主线程 RunLoop 在被唤醒并处理完必要的监控对象事件后,会尝试性地从dispatchPort中捞一下是否有需要处理的“GCD调度主线程消息”(这次调用__CFRunLoopServiceMachPort不会使 RunLoop 进入休眠,因为 timeout 参数传了0),如果有则livePort会被置为dispatchPort,并直接跳到handle_msglivePort == dispatchPort逻辑分支中立即处理。这样处理的意图应该是:

  • 保证主线程 RunLoop 的监控对象回调中的“GCD调度主线程操作”可以及时响应;
  • 在主线程已被唤醒时,可以及时响应来自其他子线程的“GCD调度主线程操作”;

总之就是保证“GCD调度主线程操作”的响应优先级。RunLoop 需要进入休眠时,dispatchPort也会被添加到waitSet中并作为__CFRunLoopServiceMachPort入参,这意味着,来自子线程的 dispatch asyn on main 的调用,也是可以唤醒主线程 RunLoop 的。

NOTE: 注意代码中形式为CFRUNLOOP_XXX_XXX全大写的宏,例如CFRUNLOOP_WAKEUP_FOR_WAKEUP,实际上啥都没做,就是相当于占位符,至于为啥非得这么干则不太清楚。

最后,再留意到代码中的poll变量,poll 这个单词在操作系统领域基本可以理解为轮询方式的多路复用机制。从代码看,当次 Loop 如果是 poll 模式时,不触发kCFRunLoopBeforeWaitingkCFRunLoopAfterWaiting事件,也就是说这次 Loop 线程不会进入休眠。此时__CFRunLoopServiceMachPort的 timeout 参数传0,和处理dispatchPort时是一样的。所以,RunLoop 中处理了 Resource0 的 Loop 是存在特殊性的,处理完 Resource0 后,RunLoop 会尝试性地从 Mode 的所有 mach port 中捞一下是否有需要处理的 mach port 事件,有则立即处理,没有则进入下一次 Loop。

NOTE: Apple Documentation 对kCFRunLoopBeforeWaiting的定义:Inside the event processing loop before the run loop sleeps, waiting for a source or timer to fire. This activity does not occur if CFRunLoopRunInMode is called with a timeout of 0 seconds. It also does not occur in a particular iteration of the event processing loop if a version 0 source fires.

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

    // NOTE: 如果RunLoop或者当前Mode状态为stopped,则无条件结束RunLoop。注意:修改stopped
    // 状态为true只能通过_CFRunLoopStopMode方法实现,这个方法是CF使用内部方法。
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
	return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
	rlm->_stopped = false;
	return kCFRunLoopRunStopped;
    }
    
    // NOTE: 专门用于响应“GCD调度主线程消息”的mach port。当前为主线程RunLoop,且在CommonModes中
    // 的任何一个Mode下运行时,会构建dispatchPort。非主线程不会构建该mach port,即对非主线程
    // RunLoop有:dispatchPort == MACH_PORT_NULL
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    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)) dispatchPort = _dispatch_get_main_queue_port_4CF();
    
    ...
    
    // NOTE: 用于处理RunLoop超时的逻辑。使用dispatch source timer实现。
    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);
        // NOTE: RunLoop超时逻辑的核心,RunLoop超时时间点等于当前时间+传入的seconds参数
	timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
	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;
    int32_t retVal = 0;
    do {
        voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
        voucher_t voucherCopy = NULL;

        uint8_t msg_buffer[3 * 1024];

        mach_msg_header_t *msg = NULL;
        
        // NOTE: 当前Loop所响应的Port
        mach_port_t livePort = MACH_PORT_NULL;

        ...

        // NOTE: 当前Loop等待中的Port
	__CFPortSet waitSet = rlm->_portSet;

        __CFRunLoopUnsetIgnoreWakeUps(rl);

        // NOTE: 确保处理Timers和Resource事件之前触发这两种通知,但是不确保触发这两种通知就必定有
        // 处理相应类型的事件
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        // 核心操作1:处理CFRunLoopPerformBlock所添加的Block
	__CFRunLoopDoBlocks(rl, rlm);

        // 核心操作2:处理Sources0。后面之所以要再执行DoBlocks是为了处理Sources0回调中包含
        // CFRunLoopPerformBlock的情况
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
	}

        // NOTE: 当前Loop处理了Source0事件或者指定了RunLoop只处理一次事件(second参数传0)
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        // NOTE: 如果dispatchPort非空(当前主RunLoop且在CommonModes中任意一个Mode下运 行)而且
        // 上一次循环响应的不是dispatchPort的事件。则尝试性地从dispatchPort中捞一下是否有需要处理
        // 的“GCD调度主线程消息”,有则立即`goto handle_msg`去处理。
        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;
            }

            ...

        }

        didDispatchPortLastTime = false;

        // NOTE: 当不是poll模式,且Mode中有Observer正在观察kCFRunLoopBeforeWaiting事件,才触
        // 发kCFRunLoopBeforeWaiting(和Apple Documentation一致)。
	if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        // 核心操作3:标记RunLoop开始休眠
	__CFRunLoopSetSleeping(rl);

        // NOTE: 将dispatchPort添加到waitSet
        __CFPortSetInsert(dispatchPort, waitSet);
        
	__CFRunLoopModeUnlock(rlm);
	__CFRunLoopUnlock(rl);

        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();

        ...

        if (kCFUseCollectableAllocator) {
            memset(msg_buffer, 0, sizeof(msg_buffer));
        }
        msg = (mach_msg_header_t *)msg_buffer;
        // 核心操作4:RunLoop正式进入休眠,等待waitSet中的ports接收消息。
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
        ...
        
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);

        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));

        // NOTE: 将dispatchPort从waitSet中移除
        __CFPortSetRemove(dispatchPort, waitSet);
        
        __CFRunLoopSetIgnoreWakeUps(rl);

        // 核心逻辑5:标记RunLoop结束休眠已被唤醒
	__CFRunLoopUnsetSleeping(rl);
        // NOTE: 当不是poll模式,且Mode中有Observer正在观察kCFRunLoopAfterWaiting事件,才触
        // 发kCFRunLoopAfterWaiting
	if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);

        ...

        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
        } else if (livePort == rl->_wakeUpPort) {
            // NOTE: 通过wakeup port唤醒(例如发送Source0信号后手动唤醒),在这里暂时不作任何处理
            // 在上面doSources0时才正式处理
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            ...
        }

        ...

        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            // 核心逻辑6:处理Timers
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // NOTE: 这句代码是为了解决一个Windows平台的缺陷而设置的,可以忽略
                __CFArmNextTimerInMode(rlm, rl);
            }
        } else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);

            ...

            // NOTE: 执行“GCD调度主线程消息”的回调
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            
            voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);

            // NOTE: 通过当前所响应的mach port查找到对应的Source
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
		mach_msg_header_t *reply = NULL;
                // 核心逻辑7:处理Sources1
		sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                // NOTE: 必要时处理mach port回复消息
		if (NULL != reply) {
		    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
		    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
		}

                ...
	    }
            
            _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
            
        } 

        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);

        // 核心逻辑8:处理Blocks
	__CFRunLoopDoBlocks(rl, rlm);
        
        //NOTE: 设置当次Loop完成后的RunLoop状态。检测到Mode为空则标记为Finished
	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)) {
	    retVal = kCFRunLoopRunFinished;
	}
        
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);

    } while (0 == retVal);

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

    return retVal;
}

然后再简单看下__CFRunLoopServiceMachPort的代码。一堆内核编程的代码,十分晦涩。其实只需要关注到其中的调用mach_msg函数的那句代码,注意到入参是MACH_RCV_MSG,说明这里是监听 mach port 接收消息而不是发送消息。

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->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(); }
        // 核心操作:调用mach_msg函数接收消息
        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);

        voucher_mach_msg_revert(*voucherState);
        
        *voucherState = voucher_mach_msg_adopt(msg);
        
        if (voucherCopy) {
            if (*voucherState != VOUCHER_MACH_MSG_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;
}

最后总结一下 RunLoop 的单次 Loop 处理各种监控对象的流程串联,如下图所示,从流程中刨除比较特殊的“GCD调度主线程操作”以及比较简单的 DoBlocks 操作,得到的核心流程有两种情况:

  • 左图:DoSources0 有触发其中某个 Source0 回调,这种情况会触发 RunLoop 休眠;
  • 右图:DoSources0 没有触发任何 Source0 回调,这种情况不会触发 RunLoop 休眠;

loadimage.png

NOTE: 注意上图中分叉的 DoTimes 和 DoSource1,其含义是:这次 Loop 要么处理 DoTimers 事件要么处理 DoSource1 事件,不会存在一次 Loop 既处理了 Timer 又处理了 Source1。因为livePort是单一 port,上面两种事件在单次 Loop 中是 if-else 的关系。

NOTE: 关于CFRunLoopPerformBlock需要注意一下,Mode 中不存在任何 port 专门用于唤醒 RunLoop 执行 DoBlocks 操作,所以说CFRunLoopPerformBlock将 block 添加到 RunLoop 中不会唤醒 RunLoop,且会等到 RunLoop 被唤醒后才会“顺便”执行这些 block。和 Apple Documentation 的描述是一致的。

总结

至此,基本将 RunLoop 的源码都过了一遍。在此过程中,又稍微加深了 RunLoop 的理解。例如,每个 Mode 都包含一个_timerPort用于触发mk_timer事件,RunLoop 所有 Timer 的计时事件实际上都归结到一个mk_timer上。例如,注意到 Apple Documentation 中,BeforeWaiting 和 AfterWaiting 事件并不是在每个 Loop 都会触发这个细节,并找到了对应的源码。