iOS-RunLoop详解(二):源码梳理Runloop的流程

iOS-RunLoop详解(二):源码梳理Runloop的流程

image-20210513112941125
image-20210513112959145
image-20210513113028057

RunLoop 源码分析:

我们找到CFRunLoop.c源码,发现里面有很多函数,哪一个才是我们想要的RunLoop入口函数呢?很简单,我们可以写一个- (void)touchesBegan:withEvent:方法,然后再这个方法内部打一个断点,因为我们知道系统事件都是由RunLoop来捕捉管理的.走到断点后,我们使用LLDB指令bt打印所有函数调用栈:

runloop 入口函数
runloop 入口函数

很明显,在通过触摸事件触发的函数调用栈里面,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 内部执行的流程

以下是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(@"回到主线程");
           
       });
   });
}

函数调用如下

image-20210513120842069
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容