解读CFRunLoopRef源码

        平时开发中我们接触的NSRunLoop比较多。但是,NSRunLoop的api不是线程安全的,所以在一个NSRunLoop中操作另一个NSRunLoop时要格外小心。其中CFRunLoopRef是线程安全的,而且NSRunLoop只是对CFRunLoop的封装。所以,我们可以通过分析CFRunLoop的源码工作原理,来了解NSRunLoop的原理。

       苹果已经对CoreFoundation代码开源,我们可以找到CFRunLoop的源码来分析。源码在这里

CFRunLoop入口函数

        void CFRunLoopRun(void)作为入口函数。这个函数较小贴一下源码。

void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

          可以看到,runloop在运行后,启动了一个do-while循环(以前没读到源码,面试这样说,面试官会一脸鄙夷,现在可以啪啪打脸了)。

          然后循环是否退出需要看CFRunLoopRunSpecific函数返回值判断result是否等于 kCFRunLoopRunStopped或kCFRunLoopRunFinished 。这两个值是一个枚举类型,源码也贴一下。

typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
    kCFRunLoopRunFinished = 1,
    kCFRunLoopRunStopped = 2,
    kCFRunLoopRunTimedOut = 3,
    kCFRunLoopRunHandledSource = 4
};

          所以,运行循环调用的函数是CFRunLoopRunSpecific

CFRunLoopRunSpecific函数

        这个函数需要四个参数:

       rl : 当前runloop对象。

       modeName : 默认的运行模式名称。

      seconds : 超时时间。超时时间设置为10的10次方,可以理解为没有处理完就不超时。

      returnAfterSourceHandled : 处理完当前source0后是否结束。CFRunLoop传的false,意思是会继续处理其他source0。

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)

      然后我们进入这个函数看一下,它都做了哪些事情。贴一下简化后的关键代码

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */

    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;

    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}

          首先这个函数先判断了一下这个runloop是否在dealloc,如果是就直接返回了kCFRunLoopRunFinished,也就结束了上边提到的do-while循环。

         然后根据runloop对象和mode名称获取当前的mode。判断当前的mode和kCFRunLoopEntry进行&计算条件成立,那么调用函数__CFRunLoopRun。

       如果是mode和kCFRunLoopExit进行&计算条件成立,那么就通知观察者kCFRunLoopExit。结束循环。 

      接下来说一下最重要的函数__CFRunLoopRun。

__CFRunLoopRun函数

         因为这个函数有三百多行,就不贴源代码了,还是贴一些精简后的关键代码。这是CFRunLoopRef进行消息处理的关键函数。这里去掉了端口处理、windows、disaptch_timer 支持等。

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {

    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
    return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
    rlm->_stopped = false;
    return kCFRunLoopRunStopped;
    }
    

    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {
       //定义了一个mach消息机制的变量

        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

    __CFRunLoopDoBlocks(rl, rlm);

        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {

            msg = (mach_msg_header_t *)msg_buffer;
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
                goto handle_msg;
            }
        }

    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    
    //阻塞等待
    __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);

    CFRUNLOOP_WAKEUP_FOR_TIMER();
   
    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

    sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;

    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);

    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }

    return retVal;
}

         ⚠️注意:第一行注释就显示出了CFRunloop是线程安全的。

        1、首先通过__CFRunLoopIsStopped判断runloop是否是stop状态,成立返回kCFRunLoopRunStopped。

        2、判断mode的状态位rlm->_stopped,判断runloop是否是stop状态,成立返回kCFRunLoopRunStopped。

       3、进入do-while循环,while的循环条件是0 == retVal。retVal是在进入循环前定义的初始化为0的变量,在循环内赋值为mode的不同状态,退出循环。

     4、接下来是循环内的处理过程:

  1. 通知观察者,准备处理Timers事件
  2. 通知观察者,准备处理source事件
  3. 处理非延迟的主线程调用
  4. 如果端口不为空,且有msg(source0事件),处理这个事件,如果有source1事件直接处理后转去处理消息goto handle_msg
  5. 通知observers,runloop即将进入休眠状态kCFRunLoopBeforeWaiting
  6. 进入休眠状态,阻塞等待,等待被timer或者source唤醒。
  7. 被timer或者source唤醒
  8. 通知监听者,runloop被唤醒kCFRunLoopAfterWaiting,开始处理事情
  9. 给retVal变量赋值,退出循环

       其实这就差不多是CFRunLoop的主要操作事件流程了。当然日常开发中我们可能只是需要知道一些mode在不同情况下的处理或者实现一条常驻线程等用法即可。捋一遍源码还是能增加一些对内部实现过程的理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Runloop的工作:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。 Osx/ios提供了两种runloop对象 : CFRunLoopRef是在CoreFoundation框架呢,是纯C函数的API,是线程安全的 NSRunLoop是基于CFRunLoopRef的封装,不是线程安全 CFRunLoopRef的线程安全性:CFRunLoopRef不是在线程刚创建的时候创建的,而是在线程获取的时候创建的,如果不主动获取线程,一直不会有。CFRunLoopRef在创建时通过static CFSpinLock_t loopsLock锁住CFRunLoopRef对象,所以CFRunLoopRef是安全的。 一个runloop包含若干个mode。每个mode又包含若干个Source/Timer/Observer CFRunLoopSourceRef是事件产生的地方。Source有两个版本: Source0和Source1。 Source0只包含一个回调,它不能主动出发事件,需要调用CFRunLoopSOurceSignal(source)将source标记,然后手动调用CFRunloopWakeUp()来唤醒RunLoop Source1包含了一个mach_port和一个回调,被用于通过内核和其他线程来发送消息,这种source能主动唤醒runloopCFRunLoopTimerRef是基于时间的触发器,它和NSTimer是toll-free bridged的,可以混用,包含一个时间长度和一个回调。当其加入到RunLoop时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒执行回调。 CFRunLoopObserverRef是观察者,每个Observer都包含了一个回调,当RunLoop的状态发生变化时,观察者就能通过回调接受到这个变化。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值