iOS Runloop(源码解析)

前言

上一篇文章主要讲解了Runloop的理论相关的知识,今天这篇文章主要从源码的角度来解析Runloop[CF-1153.18源码

源码解析

CFRunLoopRun

runloop 开始运行的时候调用CFRunLoopRun方法

截屏2021-09-28 下午3.24.05.png

  • 我们看到最外层是一个do while循环
  • 循环内调用CFRunLoopRunSpecific方法,返回一个result
  • 如果状态为:kCFRunLoopRunStoppedkCFRunLoopRunFinished,则退出runloop运行

CFRunLoopRunSpecific

截屏2021-09-28 下午3.56.24.png

  • 首先通过__CFRunLoopFindMode方法找到对应的mode,如果没有就创建
  • 如果找不到,就会直接退出,所以runloop必须依赖mode来运行

截屏2021-09-29 上午9.29.40.png

  • _per_run_data是runloop每一次run的时候的相关数据
  • 通知观察者,进入runloop
  • 调用__CFRunLoopRun开始run
  • 通知观察者,退出runloop

真正的核心代码就是在__CFRunLoopRun方法里面,下面重点看一下__CFRunLoopRun

__CFRunLoopRun

第一步: 截屏2021-09-29 上午10.46.42.png

  • 获取系统启动后的CPU运行时间,用于控制超时时间
  • 状态判断,如果mode是stop状态则不进入循环

第二步: 截屏2021-09-29 上午10.57.35.png

  • mach端口,在内核中,消息在端口之间传递。 初始为0
  • 判断是否是主线程&&当前roop是否是mainRunloop&&当前model是否是commonMode
  • 如果满足以上条件:则给mach端口赋值为主线程收发消息的端口

第三步:

截屏2021-09-29 上午11.16.21.png

  • mode赋值为dispatch端口_dispatch_runloop_root_queue_perform_4CF
  • 用在MacOX上

第四步:使用GCD实现runloop超时功能 截屏2021-09-29 上午11.36.29.png

  • seconds是设置的runloop超时时间,一般为1.0e10,所以不会超时

进入内层do while循环

这个里面的源码可以精简一下,因为有些不是在iOS平台上的


do {
// 消息缓冲区,用户缓存内核发的消息
uint8_t msg_buffer[3 * 1024]; 
//取所有需要监听的port
__CFPortSet waitSet = rlm->_portSet;
//设置RunLoop为可以被唤醒状态
__CFRunLoopUnsetIgnoreWakeUps(rl);

//1.通知 Observers: RunLoop 即将处理 Timer 回调。
if (rlm->_observerMask & kCFRunLoopBeforeTimers) 
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

//2。通知 Observers: RunLoop 即将触发 Source0 (非port) 回调
 if (rlm->_observerMask & kCFRunLoopBeforeSources) 
 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

// 执行被加入的block
//外部通过调用CFRunLoopPerformBlock函数向当前runloop增加block。新增加的block保存咋runloop.blocks_head链表里。
//__CFRunLoopDoBlocks会遍历链表取出每一个block,如果block被指定执行的mode和当前的mode一致,则调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__执行之
 __CFRunLoopDoBlocks(rl, rlm);

//RunLoop 触发 Source0 (非port) 回调
// __CFRunLoopDoSources0函数内部会调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__函数
//__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__函数会调用source0的perform回调函数,即rls->context.version0.perform
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);

//处理了source0后再次处理blocks
if (sourceHandledThisLoop) {
     __CFRunLoopDoBlocks(rl, rlm);
 }
//
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
didDispatchPortLastTime = false;

//如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
     goto handle_msg;
}

//通知oberver即将进入休眠状态
if(!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);

//接收waitSet端口的消息
//等待接受 mach_port 的消息。线程将进入休眠
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

 // 计算线程沉睡的时长
 rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
 
 __CFPortSetRemove(dispatchPort, waitSet);
 __CFRunLoopSetIgnoreWakeUps(rl);
  // runloop置为唤醒状态
 __CFRunLoopUnsetSleeping(rl);
  // 8. 通知 Observers: RunLoop对应的线程刚被唤醒。
 if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

//收到处理的消息进行处理
handle_msg:;
// 忽略端口唤醒runloop,避免在处理source1时通过其他线程或进程唤醒runloop(保证线程安全)
__CFRunLoopSetIgnoreWakeUps(rl);

if(MACH_PORT_NULL == livePort){
// livePort为null则什么也不做

}else if(livePort == rl->_wakeUpPort){
// livePort为wakeUpPort则只需要简单的唤醒runloop(rl->_wakeUpPort是专门用来唤醒runloop的)
 CFRUNLOOP_WAKEUP_FOR_WAKEUP();

}else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort){
    //如果是一个timerPort
// 如果一个 Timer 到时间了,触发这个Timer的回调
// __CFRunLoopDoTimers返回值代表是否处理了这个timer
 if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
     __CFArmNextTimerInMode(rlm, rl);
 }
}else if(livePort == dispatchPort){
   //如果是GCD port
   //处理GCD通过port提交到主线程的事件
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}else{
    // 处理source1事件(触发source1的回调)
    //// runloop 触发source1的回调,__CFRunLoopDoSource1内部会调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
   __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}


/// 执行加入到Loop的block
 __CFRunLoopDoBlocks(rl, rlm);
        
if (sourceHandledThisLoop && stopAfterHandle) {
     /// 进入loop时参数说处理完事件就返回。
     retVal = kCFRunLoopRunHandledSource; // 4
} else if (timeout_context->termTSR < mach_absolute_time()) {
            /// 超出传入参数标记的超时时间了
            retVal = kCFRunLoopRunTimedOut; // 3
        } else if (__CFRunLoopIsStopped(rl)) {
            /// 被外部调用者强制停止了
            __CFRunLoopUnsetStopped(rl); // 2
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            // 调用了_CFRunLoopStopMode将mode停止了
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped; // 2
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            // source/timer/observer一个都没有了
            retVal = kCFRunLoopRunFinished; // 1
        }



}while(0 == retVal)

复制代码

Runloop回调处理

下面主要讲解Runloop真正做事情的处理方法,通过上面的源码我们发现Runloop真正做事的时候会调用下面的方法:

__CFRunLoopDoBlocks
__CFRunLoopDoTimers
__CFRunLoopDoSources0
__CFRunLoopDoSource1
__CFRunLoopDoObserver1
复制代码

__CFRunLoopDoBlocks

这个方法主要是处理Blocks回调,下面主要粘贴部分源码进行讲解

截屏2021-09-30 上午10.21.04.png

截屏2021-09-30 上午10.21.15.png

  • runloop对象有一个_block_item结构的链表,里面存储了当前runloop相关的block
  • 遍历链表,如果block被指定的model与当前runloop的mode一样则执行block
  • 调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__执行block

__CFRunLoopDoTimers

截屏2021-09-30 上午10.56.48.png 截屏2021-09-30 上午10.57.40.png 截屏2021-09-30 上午10.57.49.png

  • 遍历runloop对象Timesr数组,把时间到期的加入到新数组
  • 遍历数组执行__CFRunLoopDoTimer
  • 调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__,执行回调方法

__CFRunLoopDoSources0

  • source0App内部事件,由App自己管理的,像UIEventCFSocket都是source0
  • source0不能主动触发,需要调用CFRunLoopWakeUp(runloop) 来唤醒 RunLoop
  • 框架已经帮我们做好了这些调用,比如网络请求的回调、滑动触摸的回调

截屏2021-09-30 上午11.27.15.png 截屏2021-09-30 上午11.28.30.png 截屏2021-09-30 上午11.28.45.png

  • 通过rlm Mode获取source0,然后转成sources
  • sources可能只有一个,也有可能是一个数组,根据不同的情况执行。
  • 调用之前判断source时候__CFRunLoopSourceIsSignaled是否已经标记
  • 调用CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION(rls->_context.version0.perform, rls->_context.version0.info)执行回调,其中perform是要执行的函数,info是参数

__CFRunLoopDoSource1

  • RunLoop和内核管理,Mach port驱动,如CFMachPortCFMessagePort
  • source1包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息
  • 能主动唤醒 RunLoop 的线程

调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__执行回调方法 截屏2021-09-30 下午2.28.11.png

调用 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm);其内部调用mach_msg()函数完成mach内核的状态改变,即使runloop休眠,然后调用source1

__CFRunLoopDoObservers

截屏2021-09-30 下午3.09.27.png 截屏2021-09-30 下午3.18.58.png

  • 通过mode获取observers
  • 遍历调用__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__方法

Runloop的应用

  1. Runloop起死回生
  2. Runloop检测卡顿
  3. NSTimer不准确
  4. 线程保活

下面提供了一个demo Runloop

猜你喜欢

转载自juejin.im/post/7013626222026424334