RunLoop原理以及使用场景

922 阅读3分钟

RunLoop学习记录

什么是RunLoop

运行循环,保持程序的持续运行,不退出,处理APP的跟中事件 runloop源码下载地址:opensource.apple.com/tarballs/CF…

基本作用

  • 保持程序的持续运行,
  • 处理APP中的各种事件(触摸,定时器,GCD异步回到主线程,runloop中block回调的处理等)
  • 节省CPU资源,提供性能,改做事的时候做事,改休息的时候休息

RunLoop对象

iOS中提供了2套Api来访问runlopp

  1. Fundation : NSRunLoop
      [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
      [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
  1. Core Fundation : CFRunloop
     CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
     CFRunLoopGetMain(); // 获得主线程的RunLoop对象

NSRunLoop是基于CFRunloop的一层OC包装

RunLoop与线程

  • 每条线程都有唯一的一个与之对应的RunLoop对象
  • RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

image.png

  • 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建

image.png

  • RunLoop会在线程结束时销毁

image.png

RunLoop相关的类

Core Foundation中关于RunLoop的5个类

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoop 的结构体

struct __CFRunLoop {
    pthread_t _pthread;  // runloop和线程是一一对应关系,每个runloop内部会保留一个对应的线程
    CFMutableSetRef _commonModes;  //标记为common的mode的集合
    CFMutableSetRef _commonModeItems;  //commonMode的item集合
    CFRunLoopModeRef _currentMode;  // 当前的模式
    CFMutableSetRef _modes; // CFRunLoopModeRef类型的集合,相对NSArray有序,Set为无序集合
};

CFRunLoopMode

  • 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会立马退出
目前已知的Mode有5种
  1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  3. UIInitializationRunLoopMode:在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  4. GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到
  5. kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {

    CFMutableSetRef _sources0;

    CFMutableSetRef _sources1;

    CFMutableArrayRef _observers;

    CFMutableArrayRef _timers;

    CFMutableDictionaryRef _portToV1SourceMap;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
};

image.png

runloop 运行逻辑
  • CFMutableSetRef _sources0;
    1. 触摸事件
    2. perform selectors

image.png

image.png

  • CFMutableSetRef _sources1;  
    1. 基于port的线程间通讯
  • CFMutableArrayRef _observers;
    1. 监听器
    2. 用于监听runloop状态变化

image.png

image.png

  • CFMutableArrayRef _timers;
    1. 定时器, NSTimer image.png

核心代码解析(简化版)

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 (runloop有个方法可以直接处理block)
      __CFRunLoopDoBlocks(rl, rlm);
       //  处理Sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
    }
        // 如果有source1 就跳转到handle_msg标记处
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            goto handle_msg;
        }
        // 通知Observers: 即将休眠
     __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
       // 进入休眠 等待其他消息唤醒
     __CFRunLoopSetSleeping(rl);
     __CFPortSetInsert(dispatchPort, waitSet);
     do {
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
     } while (1);
        // 醒来了
        __CFPortSetRemove(dispatchPort, waitSet);
        __CFRunLoopUnsetSleeping(rl);
        // 通知Observers: 已经唤醒
       __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        handle_msg:; // 看谁唤醒runloop ,进行响应处理
       // 被timer唤醒
        if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
        }else if (livePort == dispatchPort) { // 被GCD唤醒
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else { // 被source1唤醒
           __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
        }
        // 执行blocks
       __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;
    }
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);
    } while (0 == retVal);

    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }
    return retVal;
}
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
    // 通知Observers: 进入loop
   __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // 核心逻辑
   __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 通知Observers: 退出loop
  __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;
}
void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

底层运行逻辑流程图 image.png

RunLoop应用场景

  • 控制线程生命周期(线程保活)
  • 解决NSTimer在滑动时停止工作的问题
  • 监控应用卡顿
  • 性能优化
  • 闪退处理