貳: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);

} ```