iOS内存管理 —— Runloop

426 阅读4分钟

1. Runloop

Runloop 介绍

RunLoop称为事件处理循环,是线程相关的基础框架的一部分,用于安排工作和协调接收传入事件。应用在运行过程中会产生大量的系统和用户事件,包括定时器事件,用户交互事件(鼠标键盘触控板操作),模态窗口事件,各种系统Source事件,应用自定义的Source事件等等,每种事件都会存储到不同的FIFO先进先出的队列,等待事件循环依次处理。RunLoop的目的是在有工作要做的时候让线程保持忙碌,在没有工作的时候让线程休眠。

  • block应用:CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
  • 调用timer:CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
  • 响应source0: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
  • 响应source1: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
  • GCD主队列:CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
  • observer源: CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION

运行后在timer里面打断点,发现调用了__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

在这里插入图片描述

普通do while 循环可以看到这里占用了15%左右的cpu。

在这里插入图片描述

而runloop则基本为0。

在这里插入图片描述

Runloop 底层分析

在底层搜索CFRunLoop,看到这里会调用CFRunLoopGetCurrent

在这里插入图片描述

这里会尝试去或者runloop,如果没有,则会从当前线程获取。

在这里插入图片描述

这里如果t==0,那么代表着是主线程,那么就会调用pthread_main_thread_np获取主线程。后面还会创建一个可变字典,然后调用__CFRunLoopCreate创建runloop,再把主线程和创建的runloop放到创建的字典里面。

在这里插入图片描述

如果不是主线程,则进行一样的操作,只是线程变成了当前线程。

在这里插入图片描述

也就是说线程和runloop会有绑定关系:

在这里插入图片描述

接下来看__CFRunLoopCreate,这里可以在下面副赋值看到CFRunLoopRef的成员变量。

在这里插入图片描述

点击属性,看到了整个结构体。这里可以知道,CFRunLoop是一个结构体,里面有很多属性,看到这里_commonModes和_commonModeItems都是集合类型。

在这里插入图片描述

而平时往RunLoop添加事务的话,就会指定一个mode,而这个mode在添加别的事务的时候也能使用,那么也就是说,mode和事务是一对多的关系。

在这里插入图片描述

在这里插入图片描述

RunLoopMode结构体:

在这里插入图片描述

事务分三种类型,分别是:

  • Source
  • Observer
  • Timer

在这里插入图片描述

那么事务是如何依赖mode运行的呢?在源码中搜索addTimer,发现在__CFRunLoopAddItemsToCommonMode里面有调用CFRunLoopAddTimer,同时发现了这里也证明了事务分三种类型。

在这里插入图片描述

接下来查看CFRunLoopAddTimer,看这里是commonModes 下的情况。这里会获取Runloop里面的_commonModes集合,然后判断Runloop里面的事务(_commonModeItems)是否为空,为空的话就重新创建一个set复制给Runloop_commonModeItems,不为空则直接加到_commonModeItems集合里面。

在这里插入图片描述

这里只是添加timer,那么在那里运行timer呢?看到CFRunLoopRun里面调用了CFRunLoopRunSpecific

在这里插入图片描述

CFRunLoopRunSpecific里面调用了__CFRunLoopRun,并且在前后进行了状态改变的通知。

在这里插入图片描述

__CFRunLoopRun里面调用了__CFRunLoopDoTimers

在这里插入图片描述

__CFRunLoopDoTimers里面会遍历Runloop里面的timers,然后将符合条件的timer加入到timers里面,遍历完之后,遍历timers,然后执行timers里面的timer,也就是调用__CFRunLoopDoTimer

在这里插入图片描述

之后__CFRunLoopDoTimer里面就会调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__触发回调。

在这里插入图片描述

所以这里的流程是:

  • CFRunLoopAddTimer
  • CFRunLoopRun
  • CFRunLoopRunSpecific
  • __CFRunLoopRun
  • __CFRunLoopDoTimers
  • __CFRunLoopDoTimer
  • __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

2.3 Runloop的原理

继续看到CFRunLoopRun,这里参数传了一个1.0e10也就是1* 10^10作为超时时间。

在这里插入图片描述

之前看到调用__CFRunLoopRun的时候就看到有两个状态的通知。

在这里插入图片描述

点进去看除了kCFRunLoopEntrykCFRunLoopExit 还有其他的状态。其中kCFRunLoopBeforeWaitingkCFRunLoopAfterWaiting使用的最经常,代表着休眠之前和休眠之后。

在这里插入图片描述

接着看__CFRunLoopRun。 这里面用seconds进行了timeout_context的标记,然后创建了一个GCD Source的Timer,然后设置超时回调。

在这里插入图片描述

往下走看到了一个doWhile循环,Runloop大部分的事务都是在这个doWhile循环里面处理的。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

缩略代码: 在这里插入图片描述

//核心函数
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){
    
    //通过GCD开启一个定时器,然后开始跑圈
    dispatch_source_t timeout_timer = NULL;
    ...
    dispatch_resume(timeout_timer);
    
    int32_t retVal = 0;
    
    //处理事务,即处理items
    do {
        
        // 通知 Observers: 即将处理timer事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        // 通知 Observers: 即将处理Source事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
        
        // 处理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 处理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        // 处理sources0返回为YES
        if (sourceHandledThisLoop) {
            // 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        // 判断有无端口消息(Source1)
        if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
            // 处理消息
            goto handle_msg;
        }
        
        
        // 通知 Observers: 即将进入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        // 等待被唤醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
        
        // user callouts now OK again
        __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 if (被source1唤醒){
            // 被Source1唤醒,处理Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }
        
        // 处理block
        __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;
}

流程图: 在这里插入图片描述