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
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容