RunLoop 四: RunLoop源码学习

一、前言

  • 因为RunLoop的源码涉及到C语言,所以比较抽象。
  • 在苹果网站上下载 RunLoop 的源码,地址为:https://opensource.apple.com/tarballs/CF/
  • 找到 CFRunLoop.c 文件,进行学习。
  • 需要找到RunLoop 的入口文件,也就是上个笔记上写的 ‘通知Observers:进入Loop’ 这句话。

二、如何找到 RunLoop 的入口

  1. 新建一个iOS项目,在ViewController.m 文件中 写一个 touchesBegan:withEvent 方法,在这个方法中打上断点.
  2. 为何在 这个方法中打上断点,因为 点击事件就是由 RunLoop来处理的。在这个方法中打上断点,就可以看到到底是RunLoop的哪一个函数来进行调用的。也就是要在 这个方法中看下函数调用栈。
  3. 函数调用栈
    函数调用栈
  4. 可以看到函数调用栈是从下往上开始查找的。最开始的入口是 UIApplicationMain, 依次是:GSEventRunModalCFRunLoopRunSpecific__CFRunLoopRun__CFRunLoopDoSources0__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__、直到 调用 touchesBegan:withEvent:方法
  5. 我们可以在runloop.c文件中查找 CFRunLoopRunSpecific函数。也就是 用 CFRunLoopRunSpecific函数当 runloop的入口。
  6. 可以在CFRunLoop.c文件中搜索到函数: SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)

三、理顺 CFRunLoopRunSpecific 函数的流程
注: 只保留 CFRunLoopRunSpecific 中主要的代码。

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    
    // 通知 Observers: 进入 Loop
    // currentMode : 进入到某种模式
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    // 具体要做的事情
    // __CFRunLoopRun 这个可以在函数栈中看到。
	result  == __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    // 通知 Observers: 退出 Loop
     __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}

四、__CFRunLoopRun 函数做了哪些事情

  1. 在这个函数中你会看到一个 do { ... ... } while(0 == retVal)的循环。这个循环的含义是,如果 do 后面的大括号 里面的代码返回值等于 0 (也就是 while 条件成立的话),会一直执行 do 里面的代码
  2. 如果 条件一直成立,它就会一直循环的做do里面的事情。
  3. do while 循环 里面的内容,就是 runloop 具体要做的事情
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    
    do {

        // 通知 Observers: 即将处理 Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        // 通知 Observers: 即将处理Sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        // 处理 blocks
        __CFRunLoopDoBlocks(rl, rlm);

        // 处理 source0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        // 如果返回 Yes ,再次处理blcoks
        if (sourceHandledThisLoop) {
            // 处理 blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        // 判断 有没有 Sources1
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            // 如果有 Sources1, 就跳转到 handle_msg
            goto handle_msg;
        }

        didDispatchPortLastTime = false;

        // 通知 Observers: 即将休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
	
        do {
            // 等待别的消息来唤醒当前线程
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            // 如果没有消息来唤醒,就会阻塞在这里。一直等到消息来唤醒才会往下执行代码。
        } while (1);
        
        __CFRunLoopUnsetSleeping(rl);
        // 通知 Observers:结束休眠
        __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);
        
        // 设置返回值
        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;
        }
        
    } while (0 == retVal);

    return retVal;
}

五、 RunLoop 每个阶段里面所调用的函数
在这里插入图片描述

每一个步骤里面都会调用一个很长的方法,这个方法是处理 UIKit 的。
01、通知Observers:进入Loop:

  • __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

04、处理Blocks:

  • __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__

08、通知Observers:结束休眠(被某个消息唤醒)

  • 01> 处理Timer:
    • __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
  • 02> 处理GCD Async To Main Queue :
    • __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
  • 03> 处理Source1 :
    • __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__

09、处理Blocks

  • __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__

例如:
(一)、 NSTimer

  • 当到了_CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__,在上面的 #1 则是 Foundation 框架的 NSFireTimer 了。
    在这里插入图片描述

(二)、特殊的GCD

  • 一般来说 GCD 有自己的逻辑来处理。
  • GCD 有很多东西是不依赖 RunLoop 处理的。
  • 也就是说: GCD 是 GCD ; RunLoop 是 RunLoop 。 他们是分开的。
  • 但GCD 有一种情况,会交给 RunLoop 来处理。如下图:
    RunLoop处理GCD
  • 打印它的函数栈
    GCD的函数栈

六、休眠的细节

(一)、复习

  1. 07:通知Observers:开始休眠(等待消息唤醒) 这条如果没有消息,就开始睡觉。
  2. 开始睡觉,可以当成是线程阻塞,不会继续往下走。
  3. 代码为:
        do {
            // 等待别的消息来唤醒当前线程
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            // 如果没有消息来唤醒,就会阻塞在这里。一直等到消息来唤醒才会往下执行代码。
        } while (1);
  1. 休眠意味着当前线程不做事情,CPU 不会给 当前线程任何资源,当前线程就没有事情可做,就睡觉。代码就会停留在这个位置,不会再往下走。
  2. 一旦用户唤醒它,才会继续往下走。线程开始做事情。

(二)、线程阻塞是如何实现的

  1. 如果 写成 while(1){... ... } 形式的线程阻塞,是通过代码进行阻塞,当前线程并没有休息。因为这句代码是死循环,一直在判断条件,条件成句,执行语句;条件成句,执行语句;一直在执行代码,相当于当前线程一直在执行代码,没有休息。这样 cpu 并没有休息。

  2. __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy) 这句代码,会直接让 当前线程睡觉。

  3. 怎么会达到这种效果?

    • 在 RunLoop.c -> __CFRunLoopServiceMachPort 方法中,有 mach_msg(msg,MACH_RCV_MSG|...)这行代码
    • 当看到 mach_msg 代表 内核 函数。它就能直接睡觉。
    • 要想达到直接睡觉,只有内核层面才能办到。
  4. API 分为:

    • 内核层面API : 非常底层的API,操作系统底层的API。
      • 例如: 让系统直接死掉的API、让系统直接休眠的API或者直接操作硬件层面的一些代码。
      • 一般不开放给 程序员 使用。因为比较危险。
    • 应用层面API :
      • 开放给 程序员使用。可以搭建一些界面、发送网络请求之类的。
  5. RunLoop休眠的实现原理

  • 有两大类,用户方面和内核方面。当用户调用mach_msg()方法时,mach_msg() 会直接调用 内核里面的 mach_msg().
  • 内核的 mach_msg()方法等待消息。如果没有没有消息就让线程休眠,有消息就唤醒线程。
  • 当有消息的时候,内核的 mach_msg() 会告诉 用户 , 处理消息。
    在这里插入图片描述

七、面试题:

  1. runloop内部实现逻辑?
  • 就是上边的运行逻辑图
  1. runloop和线程的关系?
  • 一对一的关系
  1. runloop 是怎么响应用户操作的, 具体流程是什么样的?
  • 首先是由 Source1 捕捉系统事件。
  • 相当于一旦用户点击屏幕,先是由 Source1 去处理这个事件,然后 Source1 把这个事件包装事件队列 EventQueen,.
  • 事件队列 EventQueen 是由 source0 处理。
  1. 说说runLoop的几种状态说说runLoop的几种状态
  • kCFRunLoopEntry: 进入 RunLoop 循环
  • kCFRunLoopBeforeTimers: 处理 定时器(Timers
  • kCFRunLoopBeforeSources: 处理 Sources
  • kCFRunLoopBeforeWaiting: 休眠之前
  • kCFRunLoopAfterWaiting: 被唤醒
  • kCFRunLoopExit: 退出当前 RunLoop
  1. runloop的mode作用是什么?
  • kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
  • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响

作用:

  • 隔离。
  • 将不同的Source0/Source1/Timer/Observer隔离开来。
    这样每一组都不会互相影响。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值