美文网首页
深入理解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分析

    RunLoop 参考:深入理解RunLoop ibireme:《深入理解RunLoop》 Runloop 的概念 ...

  • 深入理解runloop

    原文 深入理解runloop 深入理解RunLoop 由 ibireme | 2015-05-18 | iOS, ...

  • RunLoop学习笔记

    参考深入理解RunLoop深入研究 Runloop 与线程保活RunLoop分享by孙源 RunLoop的概念 R...

  • RunLoop 记录

    深入理解RunLoop

  • 深入理解RunLoop

    深入理解RunLoop

  • NSRunLoop

    深入理解RunLoop RunLoop深度探究(一) RunLoop深度探究(二) RunLoop深度探究(三) ...

  • iOS知识点(10)RunLoop

    深入理解RunLoop iOS---实例化讲解RunLoop iOS runloop iOS-RunLoop充满灵...

  • iOS 各种技术点网址

    RunLoop 深入理解RunLoop(作者 :ibireme)iOS线下分享《RunLoop》by 孙源@sun...

  • Runloop 详解

    Runloop 详解 参考链接: 深入理解RunLoop CFRunLoop 概念 runloop :是管理和处理...

  • Runloop学习

    深入理解RunLoop | Garan no dou

网友评论

      本文标题:深入理解Runloop

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