贰:RunLoop的内部结构

语言: CN / TW / HK

RunLoopMode

iOSRunLoop一共有几个核心的类,分别是:

  • CFRunLoop
  • CFRunLoopMode
  • CFRunLoopSource
  • CFRunLoopObserver
  • CFRunLoopTimer

RunLoopMode定义

以下是源码中的关于RunLoop以及RunLoopMode的部分定义:

```c struct CFRunLoop { ... pthread_t _pthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; ... }

struct CFRunLoopMode { ... CFMutableSetRef _source0; CFMutableSetRef _source1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; ... } ```

根据上面的RunLoop以及RunLoopMode的定义,我们可以得出以下的对应关系:一个RunLoop可以包含有多个Mode,一个mode可以包含多个Source0 & Source1 & Observer & Timer。

RunLoop的过程.001.jpeg

每次调用RunLoop的主函数的时候,只能指定其中一个Mode,这个Mode就被称问Common Mode。如果需要切换Mode, 只能退出RunLoop重新指定Mode进入。这样做是为了分割开不同组别的Source/Timer/Observer, 让其互不影响。RunLoop一次只在一个Mode下运行。

CFRunLoopSourceRef

Source是RunLoop的数据源(input source)的一个抽象类,Source有两个版本:Source 0和Source 1。

```c struct __CFRunLoopSource { CFRuntimeBase _base; uint32_t _bits; pthread_mutex_t _lock; CFIndex _order; / immutable / CFMutableBagRef _runLoops;

// 共用体
// 同一时刻,共用体只存放了一个被选择的成员
union {
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
    CFRunLoopSourceContext1 version1;   /* immutable, except invalidation */
} _context;

}; ```

通过version的不同来区分两种不同类型的Source,而这两种source也有着很明显的区别。

Source0

Souece0包含了一个回调(函数指针),它并不能直接唤醒RunLoop,只能去主动去调用方法来唤醒RunLoop。如果我们自己创建来一个Source0,想通过Source0来唤醒RunLoop,那么需要两个步骤:

  1. 发送信号:CFRunLoopSourceSignal(rs)
  2. 手动唤醒:CFRunLoopWakeUp(RunLoop.main.getCFRunLoop())

也就是说我们需要去调用CFRunLoopSourceSignal(source), 来标记这个Source正在等待处理,然后需要手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让它来处理这个事件。

Source1

它被RunLoop和kernel管理,通过mach_port来驱动(特指基于port的事件),比如CFMachPort, CFMessagePort,NSSocketPort。Mach Port是很轻量级的方式用于进程之间的通信,它可以被理解为一个通信的channel。比如点触事件,其实就是由BackService直接将IOEvent传递给处理该事件的前台进程。这就涉及到了进程间的通信,使用的就是Source1。

CFRunLoopTimerRef

基于时间的触发器,它和NSTimer是toll-free bridged的,可以混用(它的底层是基于mk_timer的)。它是会被RunLoop Mode所影响的(而GCD的timer是不会被RunLoopMode影响的)。当它被加入RunLoop之后,RunLoop将会注册与之对应的时间点,当时间点到达的时候,Runloop将被唤醒来执行回调,如果这个线程被阻塞了或者此时RunLoop不在这个Mode中,那么这个触发的时间点点并不会执行回调,将等到下一次循环的时间点被触发。

CFRunLoopObserverRef

它是观察者,每个Observer都包含了对应一个回调(函数指针),一个Mode中可以有多个观察者,当RunLoop的状态发生改变时,观察者就可以通过回调接受这个变化。可以观测的时间点有以下几个:

c typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 即将进入RunLoop kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠 kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒 kCFRunLoopExit = (1UL << 7), // 即将退出RunLoop kCFRunLoopAllActivities = 0x0FFFFFFFU };

这些source & timer & observer都统一被称为mode item,一个item可以被同时加入多个mode。但是一个item被重复加入同一个mode时是不会有效果的。如果一个mode中一个item都没有,那么RunLoop就会直接退出,不进入循环。

Common Modes

同时要注意有一个概念叫做Common Mode。一个Mode可以把自己标记为Common(将mode添加到CommonMode中)。

swift // 当然Mode中需要加observer/Timer/source let mode = CFRunLoopMode.init(rawValue: "fff" as CFString) CFRunLoopAddCommonMode(RunLoop.main.getCFRunLoop(), mode)

这样的话,每当RunLoop的内容变化时(切换Mode运行时),RunLoop会自动将commonModeItems中的Source & Observer & Timer同步到具有"Common"标记的所有Mode中。

举个例子:主线程的RunLoop中有两个预先设置的Mode,其一是kCFRunLoopDefaultMode, 其二是UITrackingRunLoopMode。这两个Mode都被标记为Common属性。DefaultMode是默认状态,TrackingMode是追踪ScrollView滑动时的状态。当创建一个Timer时,Timer会得到重复的回调。但是当滑动一个UIScrollView时,RunLoop会将Mode切换都TrackingMode,这个时候Timer的回调就不会生效了。

所以需要这个Timer在两个Mode中都可以得到回调,那么其中一种方式就是将这个Timer分别放入到这两个Mode中。还有一种方式,就是将Timer放到顶层的RunLoop的CommonModeItems中,这其中的items都会被自动更新到所有具备Common属性的Mode中。

Mode的分类

从Apple的官方文档中,我们可知其实系统提供了几个Mode:

  • kCFRunLoopDefaultMode: App的默认Mode,通常主线程是在这个Mode下运行的。
  • UITrackingRunLoopMode:界面追踪Mode,用于ScrollView的追踪,保证界面滑动不受影响
  • UIInitializationRunLoopMode:刚启动App时进入的第一个Mode,启动后不再使用
  • GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
  • KCFRunLoopCommonModes:占位的Mode

Mode实例

直接将当前线程的RunLoop在控制台输出,可以得到如下的结构:

```c CFRunLoop { current mode = KCFRunLoopDefaultMode, common modes = { UITrackingRunLoopMode, KCFRunLoopDefaultMode }

common mode items = {
    //source 0
    CFRunLoopSource = { order = -1,{
        callout = PurpleEventSignalCallback}}   //GraphicsServices
    CFRunLoopSource = { order = 0,{
        callout = HandleDelegateSource(void*)}} //WebCore
    CFRunLoopSource = { order = 0, {
        callout = __NSThreadPerformPerform}}    //Foundation
    CFRunLoopSource = { order = 0, {
        callout = FBSSerialQueueRunLoopSourceHandler}} // FrontBoardServices
    CFRunLoopSource = { order = -1,{
        callout = __eventQueueSourceCallback}}   //UIKitCore
    CFRunLoopSource = { order = 0,{
        callout = WTF::RunLoop::performWork(void*)}}  //JavaScriptCore
    CFRunLoopSource = { order = -2,{
        callout = __eventFetcherSourceCallback}} //UIKitCore

    // source 1(mach port)
    CFRunLoopSource {order = 0,  {port = 17923}}
    CFRunLoopSource {order = 0,  {port = 12039}}
    CFRunLoopSource {order = 0,  {port = 16647}}
    CFRunLoopSource {order =-1, {
        callout = PurpleEventCallback}}
    CFRunLoopSource {order = 0, {port = 2407,
        callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}
    CFRunLoopSource {order = 0, {port = 1c03,
        callout = __IOHIDEventSystemClientAvailabilityCallback}}
    CFRunLoopSource {order = 0, {port = 1b03,
        callout = __IOHIDEventSystemClientQueueCallback}}
    CFRunLoopSource {order = 1, {port = 1903,
        callout = __IOMIGMachPortPortCallback}}


    // observer
    CFRunLoopObserver {order = -2147483647, activities = 0x1, // Entry
        callout = _wrapRunLoopWithAutoreleasePoolHandler}
    CFRunLoopObserver {order = 0, activities = 0x20,          // BeforeWaiting
        callout = _UIGestureRecognizerUpdateObserver}
    CFRunLoopObserver {order = 1999000, activities = 0xa0,    // BeforeWaiting | Exit
        callout = _beforeCACommitHandler}// UIKitCore 
    CFRunLoopObserver {order = 1999000, activities = 0xa0,    // BeforeWaiting | Exit
        callout = _afterCACommitHandler} // UIKitCore
    CFRunLoopObserver {order = 2000000, activities = 0xa0,    // BeforeWaiting | Exit
        callout = CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*)}
    CFRunLoopObserver {order = -2147483648, activities = 0x46,// BeforeTimer | BeforeSource | AfterWait
        callout = __trackRunLoopTimes}   // UIKitCore  
    CFRunLoopObserver {order = 2147483647, activities = 0x20, // BeforeWaiting
        callout = __trackRunLoopTimes}   // UIKitCore  
    CFRunLoopObserver {order = 2000000, activities = 0xa0,    // BeforeWaiting | Exit
        callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
    CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exit
        callout = _wrapRunLoopWithAutoreleasePoolHandler}

    // timer
    CFRunLoopTimer {valid = Yes, firing = No, interval = 3.1536e+09, tolerance = 0, 
        next fire date = 1.17402502e+09 (504911209 @ 12130246606742925),
        callout = CA::timer_callback(__CFRunLoopTimer*, void*)} // QuartzCore
    CFRunLoopTimer {valid = Yes, firing = No, interval = 3, tolerance = 0, 
         next fire date = 669113787 (-20.825485 @ 12377085760065),
         callout = [FSPagerView.FSPagerView flipNextWithSender:]}
    }
modes = {
     CFRunLoopMode  { //kCFRunLoopDefaultMode
        sources0 =  { /* same as 'common mode items' */ },
        sources1 =  { /* same as 'common mode items' */ },
        observers = { /* same as 'common mode items' */ },
        timers =    { /* same as 'common mode items' */ },
    },

    CFRunLoopMode  { //UITrackingRunLoopMode
        sources0 =  { /* same as 'common mode items' */ },
        sources1 =  { /* same as 'common mode items' */ },
        observers = { /* same as 'common mode items' */ },
        timers =    { /* same as 'common mode items' */ },
    },
    CFRunLoopMode {  // kCFRunLoopCommonModes
        source0 = null,
        source1 = null,
        timers = null,
        observers = null,
    },
    CFRunLoopMode { // GSEventReceiveRunLoopMode
        source0 = {
            CFRunLoopSource {order = 1,
                callout = PurpleEventSignalCallback}
        },
        source1 = {},
        observers = null,
        timers = null,
    }
}
}

} ```

我们可以很明确的看到,一个RunLoop下对应有很多种不同的Mode,而每个Mode中都有对应的source,observer等元素。当然要想看到更多的Mode类型的话,可以到Apple关于RunLoop的官方描述中查看。

RunLoop的结构

接下来我直接PO一段RunLoop的源码:

```c // GitHub上开源的CoreFoundation,run方法其实还是调用了运行defaultMode的方法 extension RunLoop { public func run() { while run(mode: .default, before: Date.distantFuture) { } } ... }

// 使用指定的模式来启动RunLoop int CFRunInMode(modeName, seconds, returnAftersourceHandled) { return CFRunloopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds,
returnAftersourceHandled); }

int CFRunloopRunSpecific(runloop, modeName, seconds, returnAftersourceHandled) { // 根据mode名找到对应的mode CFRunLoopModeRef currentMode = __CFRunLoopMode(r1, modeName, false);

if (Null == currentMode || CFRunLoopModeIsEmpty(currentMode)) {
    return;
}

int result = KCFRunLoopRunFinished;

// 1、通知observers:RunLoop即将进入RunLoop (KCFRunLoopEntry)
CFRunLoopDoObservers(rl, currentMode, KCFRunLoopEntry);

result = __CFRunLoopRun(runloop, currentMode, ...);

// 内部函数
int __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled) {
    bool didDispatchPortLastTime = true;
    in retVal = 0;

    do {
        // runloopMode中的所有port
        __CFPortSet waitSet = rlm->_portSet;

        // 2、通知observers:RunLoop即将准备处理timer
        CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

        // 3、通知observers:RunLoop即将准备处理source(不基于port的即source0)
        CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        // -- 执行被加入的block
        CFRunLoopDoBlocks(rl, rlm);

        // 4、处理Source0:Runloop处理source(不基于port的source)
        bool sourceHandledThisLoop = CFRunLoopDoSources0(rl, rlm, stopAfterHandle);

        // -- 执行被加入的block
        if (sourceHandledThisLoop) {
            CFRunLoopDoBlocks(rl, rlm);
        }

        // 5、如果有Source1处于Ready状态:RunLoop跳到第九步处理Source(基于port的source)
        // 这里指定了接收消息的端口是dispatchPort:如Dispatch.main.async{}
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            bool hasMsg = CFRunLoopServiceMachPort(dispatchPort, msg, &livePort, 0);
            if (hasMsg) goto handle_msg;
        }

        didDispatchPortLastTime = false;

        // 6、通知Observers:RunLoop即将进入休眠(kCFRunLoopBeforeWaiting)
        CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

        // 7、线程进入睡眠:调用CFRunLoopServiceMachPort等待被唤醒,被以下几个唤醒
        //    * 一个基于port的source事件(·)
        //    * 一个到点要执行的timer(Timer)
        //    * RunLoop要超时了(RunLoopTimeOut)
        //    * RunLoop被显式的唤醒了
        CFRunLoopServiceMachPort(waitSet, msg, livePort, TIMEOUT_INFINITY);

        // 8、通知Observers:线程被唤醒了
        CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        // 9、收到消息,处理消息。
        handle_msg;

        // * 无port啥也不做
        if (Mach_Port_Null == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();

        // * 标记为唤醒
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();


        // * 标记为Timer唤醒:Timer/NSTimer唤醒
        } else if (livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();

            if (!_CFRunLoopDoTimers(rl, rlm, mach_absolute_timer())) {
                CFArmNextTimerInMode(rlm, rl);
            }
        // * 标记为GCD唤醒:由main_queue派发的block
        } else if (livPort === dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();

            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        // * 标记为Source1唤醒
        } else {
            CFRUNLOOP_WAKEUP_FOR_SOURCE();

            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);

            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }

        // 执行加入到runloop中的block
        CFRunLoopDoBlocks(rl, rlm);

        // 判断runloop是否结束
        // * 刚刚处理完事件
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;

        // * runloop超时了
        } else if (timeout) {
            retVal = kCFRunLoopRunTimedOut;

        // * 被外部调用者强制停止了
        } else if (CFRunLoopIsStopped(rl)) {
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;

            retVal = kCFRunLoopRunStopped;

        // * runloop的Mode中source/timer/observer一个都没有了
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;
        }

    } while (0 == retValue);
}

//10、通知Observers:RunLoop即将离开
CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

} ```

它的内部逻辑是一个while循环,可以一直使线程处于存活的状态,有事情来就处理事情,没有事情来,就处于休息状态。基于上方的流程,我也创建了一个RunLoop流程图。

11.png

根据上述的流程,我们可以梳理出相应的回调:

```c { /// 1. 通知Observers,即将进入RunLoop /// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush(); CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION(kCFRunLoopEntry); do {

    /// 2. 通知 Observers: 即将触发 Timer 回调。
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
    /// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);

    /// 4. 触发 Source0 (非基于port的) 回调。
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);

    /// 6. 通知Observers,即将进入休眠
    /// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);

    /// 7. sleep to wait msg.
    mach_msg() -> mach_msg_trap();


    /// 8. 通知Observers,线程被唤醒
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);

    /// 9. 如果是被Timer唤醒的,回调Timer
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);

    /// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
    __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);

    /// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);


} while (...);

/// 10. 通知Observers,即将退出RunLoop
/// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);

} ```