上两篇文章说了Runloop初探和Runloop获取,这篇来说说Runloop执行流程。
CFRunLoopRef:代表RunLoop的对象
CFRunLoopModeRef:代表RunLoop的运行模式
CFRunLoopSourceRef:就是RunLoop模型图中提到的输入源 / 事件源
CFRunLoopTimerRef:就是RunLoop模型图中提到的定时源
CFRunLoopObserverRef:观察者,能够监听RunLoop的状态改变
- 我们直接从
Runloop的run开始:
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);//调用RunLoop执行函数
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;// 如果RunLoop正在释放,返回完成
__CFRunLoopLock(rl);
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);//查找对应的 Mode
...
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);//通知 Observers: 进入RunLoop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);//运行循环核心
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);//通知 Observers: 退出RunLoop
...
return result;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
...
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)));//判断是否为主线程
//如果在主线程 && runloop是主线程的runloop && 该mode是commonMode
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;//GCD 当中根队列的 Port,管理着所有的子队列
if (rlm->_queue) {
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);//获取端口
...
}
#endif
...
Boolean didDispatchPortLastTime = true;//标志位默认为true
int32_t retVal = 0;//记录最后runloop状态,用于return
do {
...
__CFRunLoopUnsetIgnoreWakeUps(rl);//设置RunLoop为可以被唤醒状态
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);//2、通知observer 即将处理 Timer 事件
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);//3、通知observer 即将处理 sources 事件
__CFRunLoopDoBlocks(rl, rlm);//处理加入当前runloop的block
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);//4、处理 source0 事件
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);//处理加入当前runloop的block
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);//如果有Sources0事件处理 或者 超时,poll都为true
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
msg = (mach_msg_header_t *)msg_buffer;
//读取消息, 不睡眠当前线程(timeout:0)
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;//从dispatchPort读取消息
}
#elif DEPLOYMENT_TARGET_WINDOWS
...
#endif
}
didDispatchPortLastTime = false;
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);//8、通知 Observers: RunLoop 的线程即将进入休眠(sleep)
__CFRunLoopSetSleeping(rl);//没有事件处理就休眠
...
__CFRunLoopUnsetSleeping(rl);//9.休眠结束
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//10、被谁唤醒就处理什么事件
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
...
if (MACH_PORT_NULL == livePort) { ... } else if (livePort == rl->_wakeUpPort) { ... }
#if USE_DISPATCH_SOURCE_FOR_TIMERS //如果是定时器事件
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
//处理 Timer 事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO //如果是定时器事件
...
#endif
else if (livePort == dispatchPort) {//如果是dispatch到main queue的block
...
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);//处理主线程上的消息;GCD主线程回调的消息由Runloop处理,GCD子线程的消息由GCD处理
...
} else {
...
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
...
//处理 Source1
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
...
}
...
}
...
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;//进入run loop时传入的参数,处理完事件就返回
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;//run loop超时
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);//run loop被手动终止
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;//mode被终止
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;//mode中没有要处理的事件
}
...
} while (0 == retVal);
...
return retVal;
}

1.在最开始的时候,渲染主线程会进入一个无限循环,每一次循环会检查消息队列中是否有任务存在。
2.如果有,就取出第一个任务执行,执行完一个后进入下一次循环;
如果没有,则进入休眠状态。
3.其他所有线程(包括其他进程的线程)可以随时向消息队列添加任务,新任务会加到消息队列的末尾;
在添加新任务时,如果主线程是休眠状态,则会将其唤醒以继续循环拿取任务。
- 补充
1.RunLoop包含多个mode,每个mode包含多个sources0事件,多个sources1事件,多个timer事件,多个observer事件和多port,RunLoop总在某个mode下运行的:

每次
RunLoop启动时,只能指定其中一个运行模式(CFRunLoopModeRef),这个运行模式(CFRunLoopModeRef)被称作当前运行模式(CurrentMode)。
如果需要切换运行模式(CFRunLoopModeRef),只能退出当前Loop,再重新指定一个运行模式(CFRunLoopModeRef)进入。
这样做主要是为了分隔开不同组的输入源(CFRunLoopSourceRef)、定时源(CFRunLoopTimerRef)、观察者(CFRunLoopObserverRef),让其互不影响 。
CFRunLoopObserverRef可以监听的状态改变有以下几种:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop:1
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer:2
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source:4
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠:32
kCFRunLoopAfterWaiting = (1UL << 6), // 即将从休眠中唤醒:64
kCFRunLoopExit = (1UL << 7), // 即将从Loop中退出:128
kCFRunLoopAllActivities = 0x0FFFFFFFU // 监听全部状态改变
};
2.Source0和Source1区别:
Source0不能主动触发事件。Source0只有一个回调(函数指针),使用时先调用 CFRunLoopSourceSignal (source)将Source 标记为待处理,然后调用 CFRunLoopWakeUp (runloop) 唤醒 RunLoop,让RunLoop处理事件。
Source1 能主动唤醒 RunLoop 的线程。Source1有一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。
Sources1是用来接收、分发系统事件,然后再分发到Sources0中处理的。
- 补充2
网上有作者说流程图不严谨:应该由"5. 如果有source1,调到第9步"改成“5. 如果当前是主线程的runloop,并且主线程有事儿,跳到第9步”

- 通知
observer run loop被触发- 如果有
timers事件的话,通知observer- 如果有
source0要处理的话,通知observer- 触发所有的准备完毕的
source0- 如果当前是主线程的
runloop,并且主线程有事儿,跳到第9步- 通知
Observer runloop将进入sleep状态mach进入sleep和监听状态- 通知
observer,runloop被woke up- 如果
runloop是被唤醒,CFRUNLOOP_WAKEUP_FOR_WAKEUP- 如果用户定义的
timer被触发,处理event并重启RunLoop- 如果
dispatchPort,处理主线程- 如果一个
source1被触发,__CFRunLoopDoSource1- 继续循环或通知
observer runloop将要exited
推荐一篇通熟易懂的runloop博客:https://mp.weixin.qq.com/s/uZFbT_bBtT-ealc-UI7Agg
