学习Runloop
Threading Programming Guide
Core Foundation开源代码(包含Runloop)
Runloop处理逻辑
每次运行runloop,你的线程的runloop会自动处理之前未处理的消息,并通知相关的观察者。具体的顺序如下:
-
通知观察者runloop已经启动
-
通知观察者任何即将要开始的定时器
-
通知观察者任何即将启动的非基于端口的源
-
处理任何准备好的非基于端口的源
-
如果基于端口的源准备好并处于等待状态,则立即处理,跳至步骤9
-
通知观察者线程进入休眠
-
将线程置于休眠直到任一下面的事件发生:
某一事件达到基于端口的源
定时器启动
runloop设置的时间已经超时
runloop被显式唤醒 -
通知观察者线程将被唤醒
-
判断唤醒的事件类型并处理:
如果是用户定义的定时器事件,则处理并重新开始Runloop,跳至步骤2
如果是一个事件源,则传递事件并重新开始Runloop,跳至步骤2
如果被显式唤醒,并且还没超时,则重新开始Runloop,跳至步骤2 -
通知观察者runloop结束。
Runloop处理逻辑图

Runloop事件类型
RunLoop主要处理以下6类事件回调:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();
-
Observer事件:runloop中状态变化时进行通知。比如,CAAnimation是由RunloopObserver触发回调来布局和重绘。
-
Block事件:非延迟的
[NSObject performSelector:]
,CFRunLoopPerformBlock()
。 -
Main_Dispatch_Queue事件:GCD中dispatch到main queue的block会被dispatch到main loop执行。比如dispatch_async,dispatch_after。
-
Timer事件:延迟的
[NSObject performSelector:]
,timer事件。 -
Source0事件:处理如UIEvent,CFSocket这类事件。触摸事件其实是Source1接收系统事件后在回调
__IOHIDEventSystemClientQueueCallback()
内触发的 Source0,Source0 再触发的_UIApplicationHandleEventQueue()
。source0一定是要唤醒runloop及时响应并执行的,如果runloop此时在休眠等待系统的 mach_msg事件,那么就会通过source1来唤醒runloop执行。自定义的Source0事件,需要手动触发:
CFRunLoopSourceSignal(source); //触发事件
CFRunLoopWakeUp(runloop);//唤醒runloop处理事件
- Source1事件:系统内核的mach_msg事件。比如,CADisplayLink。
所有的事件可以分为两类:
- 非基于port的事件:Observer事件、Block事件、Source0事件
- 基于port的事件: Timer事件、Dispatch事件、Source1事件
Runloop核心源代码:
__CFRunLoopMode
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
//mode名
CFStringRef _name;
Boolean _stopped;
char _padding[3];
//source0 源
CFMutableSetRef _sources0;
//source1 源
CFMutableSetRef _sources1;
//observer 源
CFMutableArrayRef _observers;
//timer 源
CFMutableArrayRef _timers;
//mach port 到 source1 的映射,为了在runloop主逻辑中过滤runloop自己的port消息。
CFMutableDictionaryRef _portToV1SourceMap;
//记录了所有当前mode中需要监听的port,作为调用监听消息函数的参数。
__CFPortSet _portSet;
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
//使用 mk timer, 用到的mach port,和source1类似,都依赖于mach port
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
//timer触发的理想时间
uint64_t _timerSoftDeadline; /* TSR */
//timer触发的实际时间,理想时间加上tolerance(偏差)
uint64_t _timerHardDeadline; /* TSR */
};
__CFRunLoop
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
//用于手动将当前runloop线程唤醒,通过调用CFRunLoopWakeUp完成,CFRunLoopWakeUp会向_wakeUpPort发送一条消息
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
//记录了当前runloop中所有的mode名
CFMutableSetRef _commonModes;
//记录了当前runloop中所有注册到commonMode中的源
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
//记录了添加到runloop中的block,它也可以像其他源一样被runloop处理,通过CFRunLoopPerformBlock可向runloop中添加block任务。
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
__CFRunLoopRun()
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//获取系统启动之后cpu嘀嗒数
uint64_t startTSR = mach_absolute_time();
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
//mach 端口, 线程之间通信的对象
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();
//使用GCD实现runloop超时功能
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
//seconds是设置的runloop超时时间
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
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
uint8_t msg_buffer[3 * 1024];
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
__CFPortSet waitSet = rlm->_portSet;
__CFRunLoopUnsetIgnoreWakeUps(rl);
//rl->_perRunData->ignoreWakeUps = 0x0;
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//Perform blocks queued by CFRunLoopPerformBlock;
__CFRunLoopDoBlocks(rl, rlm);
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
//如果rl中有source0消息
if (sourceHandledThisLoop) {
//处理block Perform blocks newly queued by CFRunLoopPerformBlock;
__CFRunLoopDoBlocks(rl, rlm);
}
//poll标志着有没有处理source0的消息,如果没有则为false,反之为true
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//第一次进来不走这个逻辑,didDispatchPortLastTime是true
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
msg = (mach_msg_header_t *)msg_buffer;
//__CFRunLoopServiceMachPort用于接受指定端口(一个也可以是多个)的消息,最后一个参数代表当端口无消息的时候是否休眠,0是立刻返回不休眠,
//TIMEOUT_INFINITY代表休眠
//处理通过GCD派发到主线程的任务,这些任务优先级最高会被最先处理
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
goto handle_msg;
}
}
didDispatchPortLastTime = false;
//根据之前有没有处理过source0消息,来判断如果也没有source1消息的时候是否让线程进入睡眠,这里处理observer源,如果睡眠则通知Observer进入睡眠。
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 (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
//如果poll为null,且waitset中无port有消息,线程进入休眠
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
__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);
//处理observer源,线程醒来
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
//通过CFRunloopWake将当前线程唤醒
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
}
//处理timer源
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
//通过GCD派发给主线程的任务
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
//通过macPort给当前线程派发消息,处理source1
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// Despite the name, this works for windows handles as well
//过滤macPort消息,有一些消息不一定是runloop中注册的,这里只处理runloop中注册的消息,在rlm->_portToV1SourceMap通过macPort找有没有对应的runloopMode
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
mach_msg_header_t *reply = NULL;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
//当前线程处理完source1,给发消息的线程反馈消息, MACH_SEND_MSG表示给replay端口发送消息
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
}
}
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
//runloop超时时间到
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
根据源码补充处理逻辑
补充:
第4步:非基于端口的源包括Block事件,Source0事件
第5步:主要处理dispatchPort的事件。即处理通过GCD派发到主线程的任务,这些任务优先级最高会被最先处理。
第6步:runloop睡眠是有条件的
//poll标志着有没有处理source0的消息,如果没有则为false,反之为true
//如果poll为false,且waitset中无port有消息,线程进入休眠
//__CFRunLoopServiceMachPort用于接受指定端口(一个也可以是多个)的消息,最后一个参数代表当端口无消息的时候是否休眠,0是立刻返回不休眠,TIMEOUT_INFINITY代表休眠
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
第7步:定时器唤醒通过Mode里的timerPort。超时唤醒和显式唤醒,都是通过Runloop里的wakeUpPort。
每次Runloop处理完定时器回调后都会设置下一次的Timer触发时间,即通过mk_timer_arm系统调用,注册了一个系统时钟的回调:
mk_timer_arm(rlm->_timerPort, __CFUInt64ToAbsoluteTime(nextSoftDeadline));
疑问:timer只是在Runloop唤醒后才处理,而kCFRunLoopBeforeTimers通知是在睡眠前处理???
第9步:处理Source1事件,先要根据Port对象查找出已注册的Source1。
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
Source与Port的关系是在添加Source的时候建立的(CFRunLoopAddSource)
__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if (CFPORT_NULL != src_port) {
//rlm->_portToV1SourceMap是port到source1的映射,为了在runloop主逻辑中过滤runloop自己的port消息。
CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
//rml->_portSet记录了所有当前mode中需要监听的port,作为调用监听消息函数的参数。
__CFPortSetInsert(src_port, rlm->_portSet);
}
网友评论