iOS 升级打怪 - RunLoop

609 阅读4分钟

前言

摘自官网:Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.

从上文可以看出,RunLoop 是底层架构的一部分,它与线程结合工作。它的目的是为了保证你的线程在有事干的时候做事,没事干的时候休眠。

RunLoop 的代码是开源的,可以在此处下载。本文中涉及的 RunLoop 的源码版本为:CF-1153.18。

与线程的关系

  • RunLoop 与线程的关系是一一对应的。
  • 主线程的 RunLoop 是默认开启的,子线程的需要手动开启。这个我们可以通过官网的文档得以验证:Only secondary threads need to run their run loop explicitly, however. The app frameworks automatically set up and run the run loop on the main thread as part of the application startup process.
  • RunLoop 在线程结束的时候销毁。
  • RunLoop 保存在一个全局的字典中,key 是线程,value 是 RunLoop。

内部结构

CFRunLoop.c 中可以找到其底层结构,下述示例为简化版:

struct __CFRunLoop {
    ......
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    ......
};

Mode

mode 的底层数据结构,下述示例为简化版:

struct __CFRunLoopMode {
    ......
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    ......
};

通过上述代码可以看到它包含 _sources0、_sources1、_observers、_timers。

_sources0、_sources1、_observers、_timers 各代表什么含义:

  • _sources0:触摸事件的处理
  • _sources1:基于端口的线程间的通讯、系统时间的捕捉
  • _observers:监听 RunLoop 的状态、UI 刷新、autorelease pool
  • _timers:NSTimer

mode 的分类:

  • NSDefaultRunLoopMode:系统默认 mode,主线程通常在此 mode 下运行。
  • UITrackingRunLoopMode:当列表滑动时的 mode,如 ScrollView 及其子类的滑动。
  • NSRunLoopCommonModes:如果页面有 timer 和列表,那么列表滑动的时候 timer 会停止,原因就是 timer 默认是添加在 NSDefaultRunLoopMode 下的,而列表滑动的时候会退出 NSDefaultRunLoopMode 进入 UITrackingRunLoopMode,所以 timer 会停止。解决办法就是将 timer 添加到 NSRunLoopCommonModes。

需要注意的是,RunLoop 进入下一个 mode 之前会先退出上一个 mode。而且如果 mode 中没有任何 _sources0、_sources1、_observers、_timers,RunLoop 会立马退出。

RunLoop 的状态

RunLoop 的状态是一个枚举 CFRunLoopActivity

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
  • kCFRunLoopEntry:进入本次 loop
  • kCFRunLoopBeforeTimers:即将处理 timers
  • kCFRunLoopBeforeSources:即将处理 sources
  • kCFRunLoopBeforeWaiting:即将进入休眠状态
  • kCFRunLoopAfterWaiting:从休眠中唤醒
  • kCFRunLoopExit:退出 loop

执行逻辑

  • CFRunLoopRunSpecific
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { 
    ......
        // 进入 RunLoop
	if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        // RunLoop 内部执行逻辑
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        // 退出 RunLoop
	if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    ......
    return result;
}
  • __CFRunLoopRun
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    ......
    do {
        // 通知 Observer 即将处理 timer 
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 通知 Observer 即将处理 sources
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // 处理 blocks
        __CFRunLoopDoBlocks(rl, rlm);
        // 处理 source0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            // 处理 blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }

        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            // 如果存在 source1,跳往 handle_msg
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
        }
        // 通知 observer 即将开始休眠 
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        // 线程休眠
        __CFRunLoopSetSleeping(rl);
        do { ...... } while(1)

        // 结束休眠
        __CFRunLoopUnsetSleeping(rl);
        // 通知 observer 即将结束休眠 
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
            handle_msg:;
            ......
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { // 处理 timer
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    __CFArmNextTimerInMode(rlm, rl);
                }
            } else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { // 处理 timer
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    // Re-arm the next timer
                    __CFArmNextTimerInMode(rlm, rl);
                }
            } else if (livePort == dispatchPort) { // 处理 GCD
                CFRUNLOOP_WAKEUP_FOR_DISPATCH();
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
                _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
                __CFRunLoopLock(rl);
                __CFRunLoopModeLock(rlm);
                sourceHandledThisLoop = true;
                didDispatchPortLastTime = true;
            } else { 
                ......
                CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
                if (rls) { // 处理 source1
                    sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || 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;
        }
    } while (0 == retVal);
    return retVal;
}

有上述代码可以看出,RunLoop 本质上就是用的 do-while 循环来实现的。

运行逻辑流程总结:

  • 01、通知Observers:进入Loop
  • 02、通知Observers:即将处理Timers
  • 03、通知Observers:即将处理Sources
  • 04、处理Blocks
  • 05、处理Source0(可能会再次处理Blocks)
  • 06、如果存在Source1,就跳转到第8步
  • 07、通知Observers:开始休眠(等待消息唤醒)
  • 08、通知Observers:结束休眠(被某个消息唤醒)
    • 01> 处理Timer
    • 02> 处理GCD Async To Main Queue
    • 03> 处理Source1
  • 09、处理Blocks
  • 10、根据前面的执行结果,决定如何操作
    • 01> 回到第02步
    • 02> 退出Loop
  • 11、通知Observers:退出Loop

线程休眠

RunLoop 通过调用 mach_msg 将用户态切换成内核态来进行线程休眠操作。详细解释可参见此文章

实际应用

  • 线程保活
  • 解决 NSTimer 在划动时无响应的问题
  • 监控应用卡顿