RunLoop之底层探究

概述

RunLoop顾名思义就是运行循环,来保证程序一直处于程序运行状态。

在iOS中,RunLoop有很多应用,比如:

  • 定时器(Timer)、PerformSelector
  • GCD、Async Main Quene
  • 事件响应、手势识别、界面刷新
  • 网络请求
  • AutoreleasePool

这些技术底层都会用到RunLoop。

在iOS项目中main函数中都会这样写道:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

UIApplicationMain()函数中内部就会为主线程创建一个RunLoop。对于iOS程序,RunLoop也是非常重要的,比如:

  • 保持程序的持续运行
  • 处理App中各种事件(比如触摸事件、定时器事件)
  • 节省CPU资源,提高程序性能:该处理事件时唤醒,不处理时wait。

RunLoop

iOS中有2套API可以访问和使用RunLoop

  • Foundation: NSRunLoop
  • Core Foundation: CFRunLoopRef

NSRunLoop是基于CFRunLoopRef的一层OC包装

//获取当前线程的RunLoop对象
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
//获取主线程的RunLoop对象
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];

//获取当前线程的RunLoop对象
CFRunLoopGetCurrent();
//获取主线程的RunLoop对象
CFRunLoopGetMain();

RunLoop与线程

RunLoop和线程有这密切的关系

  • 每条线程都有唯一的一个与之对应的RunLoop对象
  • RunLoop保存在一个全局的Dictionary里,线程作为key, RunLoop作为value
  • 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
  • RunLoop会在线程结束时销毁
  • 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop。

对于上述的这些关系,我们可以在开源的Core Foundation中窥见一二:

// CFRunLoop.c
// 代码已经简化

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { 
  // 在全局RunLoop字典 __CFRunLoops 中,以线程作为key,获取RunLoop
  CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  // 如果loop为空
  if (!loop) {
    //则新创建loop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
    //以线程作为key,存入全局RunLoop字典中
    CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
    loop = newLoop;
    CFRelease(newLoop);
  }
}

RunLoop相关的类

Core Foundation中关于RunLoop的5个类

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

其中RunLoop的底层结构为:

typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
  pthread_t _pthread;               //与该Runloop对应的线程
  CFMutableSetRef _commonModes;     //被标记为commonModes的Mode
  CFMutableSetRef _commonModeItems; //被添加到commonModes的事件源
  CFRunLoopModeRef _currentMode;    //表示该RunLoop中正在运行的Modes
  CFMutableSetRef _modes;           //CFRunLoopModeRef类型,表示该Runloop中包含的Modes
}
Runloop示意图.png

CFRunLoopModeRef

CFRunLoopModeRef的底层结构为:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
  CFStringRef _name;
  CFMutableSetRef _source0;
  CFMutableSetRef _source1;
  CFMutableArrayRef _observers;
  CFMutableArrayRef _timers;
}
  • CFRunLoopModeRef代表RunLoop的运行模式
  • 一个RunLoop包含若干个Mode,每个Mode有包含若干个Source0/Source1/Timer/Observer
  • RunLoop启动时只能选择其中一个Mode,作为currentMode
  • 如果需要切换Mode,只能退出当Loop
  • 不同Mode的Source0/Source1/Timer/Observer能分隔开,互不影响
  • 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

常见的2种Mode

系统默认提供的Run Loop Modes有:

  • kCFRunLoopDefaultMode(NSDefaultRunLoopMode)

  • UITrackingRunLoopMode

需要切换到对应的Mode时只需要传入对应的名称即可。前者是系统默认的Runloop Mode,例如进入iOS程序默认不做任何操作就处于这种Mode中,此时滑动UIScrollView,主线程就切换Runloop到到UITrackingRunLoopMode,不再接受其他事件操作(除非你将其他Source/Timer设置到UITrackingRunLoopMode下)。

但是对于开发者而言经常用到的Mode还有一个kCFRunLoopCommonModes(NSRunLoopCommonModes),其实这个并不是某种具体的Mode,而是一种模式组合,在iOS系统中默认包含了NSDefaultRunLoopModeUITrackingRunLoopMode

注意:并不是说Runloop会运行在kCFRunLoopCommonModes这种模式下,而是相当于分别注册了NSDefaultRunLoopModeUITrackingRunLoopMode。当然你也可以通过调用CFRunLoopAddCommonMode()方法将自定义Mode放到kCFRunLoopCommonModes组合。

注意:我们常常还会碰到一些系统框架自定义Mode,例如Foundation中NSConnectionReplyMode。还有一些系统私有Mode,例如:GSEventReceiveRunLoopMode接受系统事件,UIInitializationRunLoopMode App启动过程中初始化Mode。

Source0/1 & Timers & Observer

  • Source0
    • 触摸事件的处理
    • performSelector:onThread:
  • Source1
    • 基于Port的线程间通信
    • 系统事件捕捉,比如点击事件,捕捉后封装成Source0
  • Timers
    • NSTimer
    • performSelector:withObject:aferDelay
      • 内部是用定时器实现
  • Observer
    • 用于监听RunLoop的状态
    • UI刷新(BeforeWaiting,睡眠之前)
    • Autorelease pool(BeforeWaiting,睡眠之前)

CFRunLoopObserverRef

struct __CFRunLoopObserver {
      CFRuntimeBase _base;
      pthread_mutex_t _lock;
      CFRunLoopRef _runLoop;              /* 监听的RunLoop */
      CFIndex _rlCount;
      CFOptionFlags _activities;          /* 监听的RunLoop状态 */
      CFIndex _order;                     /* 创建时传入的排序 */
      CFRunLoopObserverCallBack _callout; /* 回调 */
      CFRunLoopObserverContext _context;  /* 回调参数 */
};

CFRunLoopObserverRef可以用来监听RunLoop的状态,RunLoop的状态分为以下几种:

Runloop_Activity.png

创建自定义Observer

创建自定义Observer有两种方式

  • 第一种:使用回调函数创建

    /// 1. 创建observer
    // 第一个参数:分配存储空间,使用默认的即可:kCFAllocatorDefault
    // 第二个参数:要监听的状态(kCFRunLoopAllActivities)所有的状态
    // 第三个参数:是否持续监听
    // 第四个参数:优先级,填0即可
    // 第五个参数:回调,CFRunLoopObserverCallBack类型,
    // typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
    // 第六个参数:context,回调传参,可以NULL
     CFRunLoopObserverRef observerRef = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                                kCFRunLoopAllActivities,
                                                                YES,
                                                                0,
                                                                observerCallBack,
                                                                NULL);
        
    /// 2.将observer添加到RunLoop中,
    // kCFRunLoopCommonModes默认包括kCFRunLoopDeaultMode、UITrackingRunLoopMode
    CFRunLoopAddObserver(CFRunLoopGetMain(),
                         observerRef,
                         kCFRunLoopCommonModes);
        
    /// 3. 释放observer
    CFRelease(observerRef);
    
    /// 回调函数
    /// @param observer observer
    /// @param activity Loop activity
    /// @param info 创建observer时,传入的context
    void observerCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"kCFRunLoopEntry,即将进入Loop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"kCFRunLoopBeforeTimers,即将处理Timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"kCFRunLoopBeforeSources,即将处理Source");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"kCFRunLoopBeforeWaiting,即将进入Loop");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"kCFRunLoopAfterWaiting,刚从睡眠中唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"kCFRunLoopExit,即将退出Loop");
                break;
            default:
                break;
        }
    }
    
  • 第二中:使用Block回调创建

    /// 1. 创建observer
    // 第一个参数:分配存储空间,使用默认的即可:kCFAllocatorDefault
    // 第二个参数:要监听的状态(kCFRunLoopAllActivities)所有的状态
    // 第三个参数:是否持续监听
    // 第四个参数:优先级,填0即可
    // 第五个参数:block回调
    CFRunLoopObserverRef observerRef = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault,
                                                                          kCFRunLoopAllActivities,
                                                                          YES,
                                                                          0,
                                                                          ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
                case kCFRunLoopEntry:
                    NSLog(@"kCFRunLoopEntry,即将进入Loop");
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"kCFRunLoopBeforeTimers,即将处理Timer");
                    break;
                case kCFRunLoopBeforeSources:
                    NSLog(@"kCFRunLoopBeforeSources,即将处理Source");
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"kCFRunLoopBeforeWaiting,即将进入Loop");
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"kCFRunLoopAfterWaiting,刚从睡眠中唤醒");
                    break;
                case kCFRunLoopExit:
                    NSLog(@"kCFRunLoopExit,即将退出Loop");
                    break;
                    
                default:
                    break;
            }
        });
        
    /// 2.将observer添加到RunLoop中,
    // kCFRunLoopCommonModes默认包括kCFRunLoopDeaultMode、UITrackingRunLoopMode
    CFRunLoopAddObserver(CFRunLoopGetMain(),
                         observerRef,
                         kCFRunLoopCommonModes);
        
    /// 3. 释放observer
    CFRelease(observerRef);
    

RunLoop的运行逻辑

Runloop流程图.png

源码分析

我们可以在ViewControllerviewDidLoad打一个断点,利用lldb指令:bt查看程序调用栈

lldb_bt_demo.png

可以看出,进入RunLoop逻辑的是CFRunLoopRunSpecific函数。下面是伪代码开始分析:

//  CFRunLoopRunSpecific
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
  //通过Mode名称 ,获取RunLoop当前的Mode
  CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
      
  //通知observer: 进入Loop
  __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
  //具体要做的事情
  result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
      
  //通知observer: 退出Loop
  __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
  return result;
}

__CFRunLoopRun()

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {

  int32_t retVal = 0;
  do {
    // 通知observer: 即将处理Timer
    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
    
    // 通知observer: 即将处理Sources
    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
    
    // 通知observer: 即将处理block
    __CFRunLoopDoBlocks(rl, rlm);

    // 通知observer: 处理Source0
    if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
      // 通知observer: 即将处理block
      __CFRunLoopDoBlocks(rl, rlm);
    }
    
    // 判断有无Source1
    if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
      //如果有就跳转到 handle_msg
      goto handle_msg;
    }
    
    // 通知observer: 即将休眠
    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    __CFRunLoopSetSleeping(rl);
        
    //等待别的消息来唤醒当前线程,线程阻塞在这里
   __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
    // 如果被唤醒,继续执行
    __CFRunLoopUnsetSleeping(rl);
    // 通知observer: 结束休眠
    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

handle_msg:;
    if (/* 被timer唤醒 */) {
      //处理timer
      __CFRunLoopDoTimers(rl, rlm, mach_absolute_time()
    } else if (/* 被timer唤醒 */) {
      // 处理GCD
      __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
    } else { /* 被Source1唤醒 */
      // 处理Source1
      __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
    }

    // 设置返回值
    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;
}

RunLoop 休眠

其实对于Event Loop而言RunLoop最核心的事情就是保证线程在没有消息时休眠以避免占用系统资源,有消息时能够及时唤醒。RunLoop的这个机制完全依靠系统内核来完成,具体来说是苹果操作系统核心组件Darwin中的Mach来完成的(Darwin是开源的)。

Mach是Darwin的核心,可以说是内核的核心,提供了进程间通信(IPC)、处理器调度等基础服务。在Mach中,进程、线程间的通信是以消息的方式来完成的,消息在两个Port之间进行传递(这也正是Source1之所以称之为Port-based Source的原因,因为它就是依靠系统发送消息到指定的Port来触发的)。消息的发送和接收使用中的mach_msg()函数(事实上苹果提供的Mach API很少,并不鼓励我们直接调用这些API)。

/*
   *  Routine:  mach_msg
   *  Purpose:
   *    Send and/or receive a message.  If the message operation
   *    is interrupted, and the user did not request an indication
   *    of that fact, then restart the appropriate parts of the
   *    operation silently (trap version does not restart).
   */
  __WATCHOS_PROHIBITED __TVOS_PROHIBITED
  extern mach_msg_return_t  mach_msg(
            mach_msg_header_t *msg,
            mach_msg_option_t option,
            mach_msg_size_t send_size,
            mach_msg_size_t rcv_size,
            mach_port_name_t rcv_name,
            mach_msg_timeout_t timeout,
            mach_port_name_t notify);

mach_msg()的本质是一个调用mach_msg_trap(),这相当于一个系统调用,会触发内核状态切换。当程序静止时,RunLoop停留在

__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy)

而这个函数内部就是调用了mach_msg()让程序处于休眠状态。

RunLoop休眠原理.png

你可能感兴趣的:(RunLoop之底层探究)