美文网首页
深入理解Runloop

深入理解Runloop

作者: juriau | 来源:发表于2020-06-22 20:09 被阅读0次

    一、Runloop基础概念

    程序一般来说,运行完就会退出。所以iOS程序需要一个机制保证程序不退出保持运行状态,并可以随时响应用户触摸点击等操作。这就是Runloop。

    Runloop的基本作用就是:

    1.保持程序持续运行;在iOS程序中,程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的RunLoop,RunLoop保证主线程了不会被销毁,也就保证了程序的持续运行

    2.响应App中的各种事件(比如:触摸事件,定时器事件等)

    3.没有事件需要处理时进入休眠,节省CPU资源。

    先看一下苹果官方的图片来简单了解一下RunLoop内部运行原理

    二、Runloop与线程的关系

    苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。这两个函数的实现逻辑大概是下面这样:

    // 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
    static CFMutableDictionaryRef __CFRunLoops = NULL;
    
    CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
        if (!__CFRunLoops) {
            // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
            CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
            CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
            CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        }
    
        // 直接从一个全局的Dic根据线程获取Runloop
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
       
        if (!loop) {
             // 如果线程中还没有runloop,会创建一个runloop,并与之关联。
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
    }
    
    CFRunLoopRef CFRunLoopGetCurrent(void) {
        return _CFRunLoopGet0(pthread_self());
    }
    
    CFRunLoopRef CFRunLoopGetMain(void) {
        return _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    }
    

    从上面的代码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。

    线程刚创建时并没有 RunLoop,RunLoop 的创建是发生在第一次获取时(getCurrent)。

    三、Runloop的底层数据结构

    Core Foundation中关于RunLoop的5个类

    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef

    它们之间的关系如下:

    一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。
    每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。
    如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

    CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。

    • Source0:非基于Port的;用于用户主动触发的事件(点击button、点击屏幕、PerformSelector)
    • Source1:基于Port的;通过内核和其他线程相互发送消息(与内核相关)

    CFRunLoopTimerRef 是基于时间的触发器,当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

    CFRunLoopObserverRef是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:

    四、Runloop运行逻辑

    // 用DefaultMode启动
    void CFRunLoopRun(void) {
        CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
    }
     
    // 用指定的Mode启动,允许设置RunLoop超时时间
    int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
        return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
    }
    
    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {   
        ...
        // 通知observers:进入loop
        if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        // 具体要做的事情
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        // 通知observers:退出loop
        if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
        ...
    }
    
    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);
            // 又处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
    
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                  // 如果有source1,跳转到handle_msg
                  goto handle_msg;
            }
    
            // 通知observers:即将休眠
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        
            // 等待别的消息来唤醒当前线程
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
    
            // 通知Observers:结束休眠
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
    
            // 收到消息,处理消息。
            handle_msg:
            __CFRunLoopSetIgnoreWakeUps(rl);
    
            if (被Timer唤醒) {
                  // 处理Timer
                  __CFRunLoopDoTimers(rl, rlm, mach_absolute_time() 
            }
            else if (被GCD唤醒) {
                  // 处理GCD           
                  __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            } else { // 被Source1唤醒
                  // 处理Source1
                  sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
            }
            
            // 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
            
            // 设置返回值
            ...
        } while (0 == retVal);
        ...
        return retVal;
    }
    

    其实RunLoop 就是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。

    Runloop在没有事件时,通过切换到内核态来休眠,节省CPU资源。


    相关文章

      网友评论

          本文标题:深入理解Runloop

          本文链接:https://www.haomeiwen.com/subject/bsedtktx.html