iOS-RunLoop详解(二):源码梳理Runloop的流程
RunLoop 源码分析:
我们找到CFRunLoop.c
源码,发现里面有很多函数,哪一个才是我们想要的RunLoop
入口函数呢?很简单,我们可以写一个- (void)touchesBegan:withEvent:
方法,然后再这个方法内部打一个断点,因为我们知道系统事件都是由RunLoop
来捕捉管理的.走到断点后,我们使用LLDB
指令bt
打印所有函数调用栈:
很明显,在通过触摸事件触发的函数调用栈里面,CF框架最初是通过CFRunLoopRunSpecific
函数进入Runloop的,接下来便调用了__CFRunLoopRun
,从名字就能看出这里可定是入口了。
在CFRunLoop
中搜索CFRunLoopRunSpecific
函数,简化后如下:
// RunLoop 入口函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
//①①①①①通知 observer: 进入 loop ----------kCFRunLoopEntry
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//具体要做的事情 启动runloop ????????????
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//??????通知observer`: 退出 loop-------------kCFRunLoopEntry
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
所以我们重点研究__CFRunLoopRun
函数内部做了什么事情.
我把__CFRunLoopRun
函数精简了一部分,
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0; //??????退出do-while循环的标签retVal
do {//??????unloop的核心就是这样一个do-while循环
//????????????通知observer: 即将处理 Timers -----kCFRunLoopBeforeTimers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//????????????通知observer: 即将处理 Sources -----kCFRunLoopBeforeSources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//??????处理 blocks
__CFRunLoopDoBlocks(rl, rlm);
//????????***⑤⑤⑤⑤⑤***????????处理source0-------
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//????????????????需要的话处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
//⑥⑥⑥⑥⑥⑥---判断有无 Source1
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
//如果有 Source1 , 就直接跳转到 handle_msg
goto handle_msg;
}
didDispatchPortLastTime = false;
//⑦⑦⑦⑦⑦⑦⑦ ?????? 通知observer: 即将休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//进入休眠 (睡觉)
__CFRunLoopSetSleeping(rl);
//等待别的消息来唤醒当前线程,如果唤醒就继续往下走;如果没有被唤醒,就会阻塞在这个位置等待别人唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
//唤醒 (不睡觉)
__CFRunLoopUnsetSleeping(rl);
//⑧⑧⑧⑧⑧⑧ ?????? 通知observer: 结束休眠-----kCFRunLoopAfterWaiting 结束休眠
__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 {
//剩下的就是被 source1 唤醒
//处理 source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
//⑩⑩⑩⑩⑩⑩⑩⑩ 处理 Blocks
__CFRunLoopDoBlocks(rl, rlm);
//设置返回值,最后决定将要干什么事情????????***?***????????设置返回值retVal
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);
return retVal;
}
用下图总结一下RunLoop
内部执行的流程:
以下是RunLoop中的7个核心操作单元
- ①
__CFRunLoopDoSource1
:处理source1事件,其内部调用了
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
- ②
__CFRunLoopDoSources0
:处理source0事件,其内部调用了
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
- ③
__CFRunLoopDoObservers
:通知观察者,其内部调用了
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
- ④
__CFRunLoopDoTimers
:处理定时器事件,其内部调用了
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
- ⑤
__CFRunLoopDoBlocks
:处理blocks,其内部调用了
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
- ⑥
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
:处理GCD异步主线程任务- ⑦
__CFRunLoopServiceMachPort
休眠线程,等待消息唤醒
线程阻塞的细节:
我们上面讲的如果没有事情做的时候RunLoop
会进入休眠状态,会阻塞当前线程,不会继续往下执行.但是这里要搞清楚,RunLoop
的阻塞线程和我们平常代码写的阻塞线程可不一样,比如说下面这种阻塞线程:
while(1){
NSlog(@"阻塞线程");
}
像这种就是个死循环,阻塞线程的时候线程并没有休息,而是一直在判断条件并执行代码.而RunLoop
的阻塞是真正意义上的休息,什么事情也不管,一句代码都不执行,CPU不会分配任何资源.
那么RunLoop
是如何做到这样的呢?
因为API
的调用分为用户态和内核态.内核态的代码是非常底层的,不对外开放.如果RunLoop
要进入休眠状态的时候,用户态的应用代码会调用mach_msg()
函数,就会转到内核态,在内核态内部调用内核态的mach_msg ()
函数进入真正的休眠状态.__CFRunLoopServiceMachPort
函数是一种真正意义上的休眠,它使得当前线程真正停下来,并且不再需要占用CPU资源去执行汇编指令了.
GCD与RunLoop
GCD和RunLoop是两个独立的机制,大部分情况下是彼此不相关的。但是上面我们看到RunLoop里面有一个核心操作叫__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
,翻译过来大概是 RunLoop正在服务(GCD的)主线程队列,说明GCD讲一些事情交给了RunLoop处理。实际上,当我们从子线程异步调回到主线程执行任务时,GCD会将这个主线程任务丢给RunLoop,最后通过__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
函数传送给GCD内部去处理,下面的代码就是这种情况
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"点击屏幕");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"子线程事件");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线程");
});
});
}
函数调用如下