Runloop的字面意思是运行循环,但是我们不要忘了,Runloop其实也是一个对象,它位于CoreFoundation框架下。Runloop对象会提供一个入口函数(定义如下),当程序执行该入口函数后,就会进入一个do-while循环,这就是Runloop能使线程常驻的原因所在。
首先我们来看Runloop的结构
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // 内核向该端口发送消息可以唤醒runloop
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; //RunLoop对应的线程
uint32_t _winthread;
CFMutableSetRef _commonModes; //存储的是字符串,记录所有标记为common的mode
CFMutableSetRef _commonModeItems;//存储所有commonMode的item(source、timer、observer)
CFRunLoopModeRef _currentMode; //当前运行的mode
CFMutableSetRef _modes; //存储的是CFRunLoopModeRef
struct _block_item *_blocks_head;//doblocks的时候用到
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
从上面可以看出 _modes是一个set结构,其中中承载的是CFRunLoopModeRef
所以有结论 runloop 1对多 RunloopMode
然后来看看最重要的RunloopMode的结构
typedef struct __CFRunLoopMode * CFRunLoopModeRef;
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; //mode名称
Boolean _stopped; //mode是否被终止
char _padding[3];
//几种事件
CFMutableSetRef _sources0; //sources0
CFMutableSetRef _sources1; //sources1
CFMutableArrayRef _observers; //通知
CFMutableArrayRef _timers; //定时器
CFMutableDictionaryRef _portToV1SourceMap; //字典 key是mach_port_t,value是CFRunLoopSourceRef
__CFPortSet _portSet; //保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
从上面可以看出RunloopMode与RunloopItem(source0,source1,observers,timers)的关系,都是set与array
即1对多
所以有如下图
runloop的关系图
runloop与线程的关系
// 主运行循环
CFRunLoopRef mainRunloop = CFRunLoopGetMain();
// 当前运行循环
CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
这两种方式最终都会走到_CFRunLoopGet0
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
//pthread_main_thread_np 主线程
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
//如果t不存在,则标记为主线程(即默认情况,默认是主线程)
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
__CFSpinUnlock(&loopsLock);
//创建全局字典,标记为kCFAllocatorSystemDefault
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//通过主线程 创建主运行循环
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
//利用dict,进行key-value绑定操作,即可以说明,线程和runloop是一一对应的
// dict : key value
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFSpinLock(&loopsLock);
}
//通过其他线程获取runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
//如果没有获取到,则新建一个运行循环
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
//将新建的runloop 与 线程进行key-value绑定
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFSpinUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
很显然可以看出线程与runloop是一对一的绑定关系,只不过主线程的runloop默认是开启的,而支线程的runloop则需要手动去开启
Runloop如何实现运行循环
//核心函数
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){
//通过GCD开启一个定时器,然后开始跑圈
dispatch_source_t timeout_timer = NULL;
...
dispatch_resume(timeout_timer);
int32_t retVal = 0;
//处理事务,即处理items
do {
// 通知 Observers: 即将处理timer事件
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知 Observers: 即将处理Source事件
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 处理sources0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
// 处理sources0返回为YES
if (sourceHandledThisLoop) {
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
// 判断有无端口消息(Source1)
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
// 处理消息
goto handle_msg;
}
// 通知 Observers: 即将进入休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
// 等待被唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
// 通知 Observers: 被唤醒,结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:
if (被timer唤醒) {
// 处理Timers
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
}else if (被GCD唤醒){
// 处理gcd
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}else if (被source1唤醒){
// 被Source1唤醒,处理Source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
// 处理block
__CFRunLoopDoBlocks(rl, rlm);
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;//结束
}
}while (0 == retVal);
return retVal;
}
综上流程图
Runloop的执行流程
mach_msg
这是系统内核在某个 port 收发消息所使用的函数,理解这个函数对于理解 runloop 的运行机制非常重要。详细的说明可参考(http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_msg.html)。
值得注意的是,收消息与发消息都是调用这个函数,只是参数不同,发送为 MACH_SEND_MSG,接收为 MACH_RCV_MSG。
可以简单的将 mach_msg 理解为多进程之间的一种通信机制,不同的进程可以使用同一个消息队列来交流数据,当使用 mach_msg 从消息队列里读取 msg 时,可以在参数中 timeout 值,在 timeout 之前如果没有读到 msg,当前线程会一直处于休眠状态。这也是 runloop 在没有任务可执行的时候,能够进入 sleep 状态的原因。
Runloop 流程解析
所谓 Runloop,简而言之,是 Apple 所设计的,一种在当前线程,持续调度各种任务的运行机制。说起来有些绕口,我们翻译成代码就非常直白了。
while(alive) { performTask() //执行任务callout_to_observer() //通知外部sleep() //休眠
}
每一次 loop 执行,主要做三件事:
performTask()
callout_to_observer()
sleep()
再来依次看下上面三步的花式展开。
performTask
每一次 runloop 的运行都会执行若干个 task,执行 task 的方式有多种,有些方式可以被开发者使用,有些则只能被系统使用。逐一看下:
DoBlocks()
这种方式可以被开发者使用,使用方式很简单。可以先通过 CFRunLoopPerformBlock 将一个 block 插入目标队列,函数签名如下:
voidCFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void(^block)( void));
详细使用方式可参考文档:https://developer.apple.com/documentation/corefoundation/1542985-cfrunloopperformblock?language=objc
可以看出该 block 插入队列的时候,是绑定到某个 runloop mode 的,runloop mode 的概念后面会详细解释,也是理解 runloop 运行机制的关键。
调用上面的 api 之后,runloop 在执行的时候,会通过如下 API 执行队列里所有的 block:
__CFRunLoopDoBlocks(rl, rlm);
很显然,执行的时候也是只执行和某个 mode 相关的所有 block。至于执行的时机点有多处,后面也会标注。
DoSources0()
Runloop 里有两种 source,source0 和 source1,虽然名称相似,二者运行机理并不相同。source0 有公开的 API 可供开发者调用,source1 却只能供系统使用,而且 source1 的实现原理是基于 mach_msg 函数,通过读取某个 port 上内核消息队列上的消息来决定执行的任务。
作为开发者要使用 source0 也很简单,先创建一个 CFRunLoopSourceContext,context 里需要传入被执行任务的函数指针作为参数,再将该 context 作为构造参数传入 CFRunLoopSourceCreate 创建一个 source,之后通过 CFRunLoopAddSource 将该 source 绑定的某个 runloop mode 即可。
详细文档可参考:https://developer.apple.com/documentation/corefoundation/1542679-cfrunloopsourcecreate?language=objc
绑定好之后,runloop 在执行的时候,会通过如下 API 执行所有的 source0:
__CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
同理,每次执行的时候,也只会运行和当前 mode 相关的 source0。
DoSources1()
如上所述,source1 并不对开发者开放,系统会使用它来执行一些内部任务,比如渲染 UI。
公司内部有个厉害的工具,可以将某个线程一段时间内所执行的函数全部 dump 下来,上传到后台并以流程图的形式展示,很直观。得益于这个工具,我可以清楚的看到 DoBlocks,DoSources0, DoSources1 被使用时的 call stack,也就能知道系统是处于什么目的在使用上述三种任务调用机制,后面解释。
DoTimers()
这个比较简单,开发者使用 NSTimer 相关 API 即可注册被执行的任务,runloop 通过如下 API 执行相关任务:
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
同理,每次执行的时候,也只会运行和当前 mode 相关的 timer。
DoMainQueue()
这个也再简单不过,开发者调用 GCD 的 API 将任务放入到 main queue 中,runloop 则通过如下 API 执行被调度的任务:
_dispatch_main_queue_callback_4CF(msg);
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(void *msg) {
_dispatch_main_queue_callback_4CF(msg);
asm __volatile__(""); // thwart tail-call optimization
}
注意,这里就没有 rlm 参数了,也就是说 DoMainQueue 和 runloop mode 是不相关的。msg 是通过 mach_msg 函数从某个 port 上读出的 msg。
问题来了
综上所述,在 runloop 里一共有 5 种方式来执行任务,那么问题来了,苹果为什么要搞这么多花样,他们各自的使用场景是什么?
timer 和 mainqueue 无需多说,开发者大多熟悉其背后设计宗旨。至于 DoBlocks,DoSources0,和 DoSources1,我原先以为系统在使用时,他们各有分工,比如某些用来接收硬件事件,有些则负责渲染 Core Animation 任务,但实际观摩过一些主线程运行样本之后,我发现并无类似的 pattern。
比如我在 doSource0 里看到了这个 callstack:
...__CFRunLoopDoSources0 ...[ UIApplicationsendEvent:] ...
显然是系统用 source0 任务来接收硬件事件。
又比如这个使用 mainqueue 的 callstack:
..._dispatch_main_queue_callback_4CF...[ UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]...
系统在使用 doMainQueue 来执行 UIView 的布局任务。
再比如这个 callstack:
...__CFRunLoopDoTimer...[UICollectionView _updateWithItems:tentativelyForReordering:animator:]...
这是系统在使用 doTimers 来 UI 绘制任务。
再看这个:
...__CFRunLoopDoBlocks...CA::Context::commit_transaction(CA::Transaction*)...
这是系统在使用 doBlocks 来提交 Core Animation 的绘制任务。
继续看这个:
...__CFRunLoopDoSources0...CA::Transaction::commit() ...
这是系统在使用 doSource0 来提交 Core Animation 的绘制任务。
不知道大家看出什么 pattern 没,我没,唯一比较有规律的是硬件事件都是通过 doSource0 来传递的,总体感觉系统在使用的时候有点 free style。
callout_to_observer
这一分类主要是 runloop 用来通知外部 observer 用的,用来告知外部某个任务已被执行,或者是 runloop 当前处于什么状态。我们也来逐一看下:
DoObservers-Timer
故名思义,在 DoTimers 执行完毕之后,调用 DoObservers-Timer 来告知感兴趣的 observer,怎么注册 observer 呢?在介绍完各种 callback 机制之后,再统一说下。runloop 是通过如下函数来通知 observer:
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
DoObservers-Source0
同理,是在执行完 source0 之后,调用 DoObservers-Source0 来告知感兴趣的 observer,怎么注册后面统一介绍。runloop 通过如下函数来通知 observer:
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
这是上述五种执行任务方式中,两种可以注册 observer 的,其他几个都不支持,mainQueue,source1,block 都不行。所以理论上,是没有办法准确测量各个任务执行的时长的。
DoObservers-Activity
这是 runloop 用来通知外部自己当前状态用的,当前 runloop 正执行到哪个 activity,那么一共有几种 activity 呢?看源码一清二楚:
typedefCF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = ( 1UL << 0), kCFRunLoopBeforeTimers = ( 1UL << 1), kCFRunLoopBeforeSources = ( 1UL << 2), kCFRunLoopBeforeWaiting = ( 1UL << 5), kCFRunLoopAfterWaiting = ( 1UL << 6), kCFRunLoopExit = ( 1UL << 7), kCFRunLoopAllActivities = 0x0FFFFFFFU};
啰嗦下,再一个个讲解:
kCFRunLoopEntry
每次 runloop 重新进入时的 activity,runloop 每一次进入一个 mode,就通知一次外部 kCFRunLoopEntry,之后会一直以该 mode 运行,知道当前 mode 被终止,进而切换到其他 mode,并再次通知 kCFRunLoopEntry。runloop mode 的切换也是个很有意思的话题,后面会提到。
kCFRunLoopBeforeTimers
这就是上面提到的 DoObservers-Timer,Apple 应该是为了代码的整洁,将 kCFRunLoopBeforeTimers 也归为了一种 activity,其含义上面已经介绍,不再赘述。
kCFRunLoopBeforeSources
同理,Apple 也将该 callout 归为了一种 runloop 的 activity。
kCFRunLoopBeforeWaiting
这个 activity 表示当前线程即将可能进入睡眠,如果能够从内核队列上读出 msg 则继续运行任务,如果当前队列上没多余消息,则进入睡眠状态。读取 msg 的函数为:
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), poll ? 0: TIMEOUT_INFINITY);
其本质是调用了开篇所说的 mach_msg 内核函数,注意 timeout 值,TIMEOUT_INFINITY 表示有可能无限进入睡眠状态。
kCFRunLoopAfterWaiting
这个 activity 是当前线程从睡眠状态中恢复过来,也就是说上面的 mach_msg 终于从队列里读出了 msg,可以继续执行任务了。这是每一次 runloop 从 idle 状态中恢复必调的一个 activity,如果你想设计一个工具检测 runloop 的执行周期,那么这个 activity 就可以作为周期的开始。
kCFRunLoopExit
exit 不必多言,切换 mode 的时候可能会调用到这个 activity,为什么说可能呢?这和 mode 切换的方式有关,后面会提及。
activity 的 回调并不是单单给开发者用的,事实上,系统也会通过注册相关 activity 的回调来完成一些任务,比如我看到过如下的 callstack:
...__CFRunLoopDoObservers...[ UIView(Hierarchy) addSubview:] ...
显然系统在观测到 runloop 进入某个 activity 之后,会进行一些 UIView 的布局工作。
再看这个:
...__CFRunLoopDoObservers...[ UIViewController__viewWillDisappear:] ...
这是系统在使用 DoObservers 传递 viewWillDisappear 回调。
以上即为 observer 的全部内容,一般开发者对 runloop 的 activity 感兴趣,多半是想分析主线程的业务代码执行情况,事实上,这些 activity 的回调不怎么可靠,也就是说有可能 runloop 哼哧运行来半天的代码,你一个 activity 的回调也收不到,或者收到了,但顺序也是完全出乎你的意料,后面会详细解释。
sleep
一言以蔽之,有任务就执行,没任务就 sleep。这部分逻辑就这么简单。
只是有个小细节需要注意,一般人印象里感觉 runloop 的每次 loop 总是按顺序执行上面的各种 performTask 和 callout_to_observer,执行完就 sleep,而实际上,这些任务的执行相互糅合在一起,还有 goto 的跳转逻辑,显得非常凌乱,而且 activity 的 callback 也可能不是按照 kCFRunLoopEntry->kCFRunLoopBeforeWaiting->kCFRunLoopAfterWaiting->kCFRunLoopExit 来的,后面我会画个流程图来解释下。
Runloop 的 loop 主函数为 __CFRunLoopRun,里面的这行调用会决定是否 sleep:
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), poll ? 0: TIMEOUT_INFINITY);
其内部无非是使用了我们开篇所提到的 mach_msg 函数。
runloop sleep 的代码之处,作者还调皮的留了一句 hamlet 中的台词:
In that sleep of death what nightmares may come
完整流程
至此,我们已将 runloop 中的关键代码分为了三类,并就这三类进行了展开,接下来我们看下完整的流程。
Apple 工程师提到 runloop 的实现可能会随着 iOS 版本而变化,我在对比 Objective C 和 Swift 版本代码之后,发现关键流程没多少区别,下面这张图是我阅读代码时顺手绘制的,希望能让读者对 runloop 的运行机制有更直观形象的认识:
流程细节需要注意
Runloop细节.jpg
Poll?
每次 loop 如果处理了 source0 任务,那么 poll 值会为 true,直接的影响是不会 DoObservers-BeforeWaiting 和 DoObservers-AfterWaiting,也就是说 runloop 会直接进入睡眠,而且不会告知 BeforeWaiting 和 AfterWaiting 这两个 activity。所以你看,有些情况下,可能 runloop 经过了几个 loop,但你注册的 observer 却不会收到 callback。
两次 mach_msg
其实一次 loop 里有两次调用 mach_msg,有一次我没有标记出来,是发生在 DoSource0 之后,会主动去读取和 mainQueue 相关的 msg 队列,这不过这个 mach_msg 调用是不会进入睡眠的,因为 timeout 值传入的是 0,如果读到了消息,就直接 goto 到 DoMainQueue 的代码,这种设计应该是为了保障 dispatch 到 main queue 的代码总是有较高的机会得以运行。
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);
getpid(); // thwart tail-call optimization
}
Port Type
每次 runloop 被唤醒之后,会根据 port type 而决定到底执行哪一类任务,DoMainQueue,DoTimers,DoSource1 三者只会运行一个,剩下的会留到下一次 loop 里去执行。
参考文档:https://www.sohu.com/a/280724054_487496
附未优化过的源码
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;
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();
#if USE_DISPATCH_SOURCE_FOR_TIMERS
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
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 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
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);
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 // timer 10^10 无限 - 永动机
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
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);
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks(rl, rlm);
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
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;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__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);
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
do {
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
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);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
#else
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
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);
#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
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
// 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);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
#if DEPLOYMENT_TARGET_WINDOWS
if (windowsMessageReceived) {
// These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
if (rlm->_msgPump) {
rlm->_msgPump();
} else {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
// To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
// Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
// NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
__CFRunLoopSetSleeping(rl);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopUnsetSleeping(rl);
// If we have a new live port then it will be handled below as normal
}
#endif
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 DEPLOYMENT_TARGET_WINDOWS
// Always reset the wake up port, or risk spinning forever
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#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
__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();
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#elif DEPLOYMENT_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
}
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
__CFRunLoopDoBlocks(rl, rlm);
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;
}
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
网友评论