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 节点。
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
或者CFSet
(CFRunLoopSource
的集合),所以才会先根据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 构建逻辑稍微比较有干货。其中now1
是CFAbsoluteTimeGetCurrent
返回的当前标准时间戳,now2
是mach_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_timer
和mk_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,定时任务间的相对顺序被打乱。
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 事件;
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_msg
的livePort == 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 模式时,不触发kCFRunLoopBeforeWaiting
和kCFRunLoopAfterWaiting
事件,也就是说这次 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 ifCFRunLoopRunInMode
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 休眠;
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 都会触发这个细节,并找到了对应的源码。