iOS面試題:runloop 的 mode 作用是什么?

在 CoreFoundation 里面關(guān)于 RunLoop 有 5 個(gè)類(lèi),分別對(duì)應(yīng)不同的概念:

  • CFRunLoopRef,對(duì)應(yīng) runloop。

  • CFRunLoopModeRef,對(duì)應(yīng) runloop mode。CFRunLoopModeRef 類(lèi)并沒(méi)有對(duì)外暴露,只是通過(guò) CFRunLoopRef 的接口進(jìn)行了封裝

  • CFRunLoopSourceRef,對(duì)應(yīng) source,表示事件產(chǎn)生的地方。Source 有兩個(gè)版本:Source0 和 Source1。Source0 只包含了一個(gè)回調(diào)(函數(shù)指針),它并不能主動(dòng)觸發(fā)事件。使用時(shí),你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個(gè) Source 標(biāo)記為待處理,然后手動(dòng)調(diào)用 CFRunLoopWakeUp(runloop) 來(lái)喚醒 RunLoop,讓其處理這個(gè)事件。Source1 包含了一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針),被用于通過(guò)內(nèi)核和其他線(xiàn)程相互發(fā)送消息。這種 Source 能主動(dòng)喚醒 RunLoop 的線(xiàn)程。

  • CFRunLoopTimerRef,對(duì)應(yīng) timer,是基于時(shí)間的觸發(fā)器。它和 NSTimer 是 toll-free bridged 的,可以混用。其包含一個(gè)時(shí)間長(zhǎng)度和一個(gè)回調(diào)(函數(shù)指針)。當(dāng)其加入到 RunLoop 時(shí),RunLoop 會(huì)注冊(cè)對(duì)應(yīng)的時(shí)間點(diǎn),當(dāng)時(shí)間點(diǎn)到時(shí),RunLoop 會(huì)被喚醒以執(zhí)行那個(gè)回調(diào)。

  • CFRunLoopObserverRef,對(duì)應(yīng) observer,表示觀察者。每個(gè) Observer 都包含了一個(gè)回調(diào)(函數(shù)指針),當(dāng) RunLoop 的狀態(tài)發(fā)生變化時(shí),觀察者就能通過(guò)回調(diào)接受到這個(gè)變化??梢杂^測(cè)的時(shí)間點(diǎn)有以下幾個(gè):

  • kCFRunLoopEntry,即將進(jìn)入Loop

  • kCFRunLoopBeforeTimers,即將處理 Timer

  • kCFRunLoopBeforeSources,即將處理 Source

  • kCFRunLoopBeforeWaiting,即將進(jìn)入休眠

  • kCFRunLoopAfterWaiting,剛從休眠中喚醒

  • kCFRunLoopExit,即將退出Loop

上面的 Source/Timer/Observer 被統(tǒng)稱(chēng)為 mode item,一個(gè) item 可以被同時(shí)加入多個(gè) mode。但一個(gè) item 被重復(fù)加入同一個(gè) mode 時(shí)是不會(huì)有效果的。如果一個(gè) mode 中一個(gè) item 都沒(méi)有,則 RunLoop 會(huì)直接退出,不進(jìn)入循環(huán)。

這些概念的包含關(guān)系如下圖所示:

線(xiàn)程的運(yùn)行的過(guò)程中需要去處理不同情境的不同事件,mode 則是這個(gè)情景的標(biāo)識(shí),告訴當(dāng)前應(yīng)該響應(yīng)哪些事件。一個(gè) RunLoop 包含若干個(gè) Mode,每個(gè) Mode 又包含若干個(gè) Source/Timer/Observer。每次調(diào)用 RunLoop 的主函數(shù)時(shí),只能指定其中一個(gè) Mode,這個(gè) Mode 被稱(chēng)作 CurrentMode。如果需要切換 Mode,只能退出 Loop,再重新指定一個(gè) Mode 進(jìn)入。這樣做主要是為了分隔開(kāi)不同組的 Source/Timer/Observer,讓其互不影響。

CFRunLoopMode 和 CFRunLoop 的結(jié)構(gòu)大致如下:

    struct __CFRunLoopMode {
        CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"
        CFMutableSetRef _sources0;    // Set
        CFMutableSetRef _sources1;    // Set
        CFMutableArrayRef _observers; // Array
        CFMutableArrayRef _timers;    // Array
        ...
    };
     
    struct __CFRunLoop {
        CFMutableSetRef _commonModes;     // Set
        CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
        CFRunLoopModeRef _currentMode;    // Current Runloop Mode
        CFMutableSetRef _modes;           // Set
        ...
    };

這里有個(gè)概念叫 CommonModes:一個(gè) Mode 可以將自己標(biāo)記為 Common 屬性(通過(guò)將其 ModeName 添加到 RunLoop 的 commonModes 中)。每當(dāng) RunLoop 的內(nèi)容發(fā)生變化時(shí),RunLoop 都會(huì)自動(dòng)將 _commonModeItems里的 Source/Observer/Timer 同步到具有 Common 標(biāo)記的所有 Mode 里。

應(yīng)用場(chǎng)景舉例:主線(xiàn)程的 RunLoop 里有兩個(gè)預(yù)置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。這兩個(gè) Mode 都已經(jīng)被標(biāo)記為 Common 屬性。DefaultMode 是 App 平時(shí)所處的狀態(tài),TrackingRunLoopMode 是追蹤 ScrollView 滑動(dòng)時(shí)的狀態(tài)。當(dāng)你創(chuàng)建一個(gè) Timer 并加到 DefaultMode 時(shí),Timer 會(huì)得到重復(fù)回調(diào),但此時(shí)滑動(dòng)一個(gè) TableView 時(shí),RunLoop 會(huì)將 mode 切換為 TrackingRunLoopMode,這時(shí) Timer 就不會(huì)被回調(diào),并且也不會(huì)影響到滑動(dòng)操作,因?yàn)檫@個(gè) Timer 作為一個(gè) mode item 并沒(méi)有被添加到 commonModeItems 里,所以它不會(huì)被同步到其他 Common Mode 里。

有時(shí)你需要一個(gè) Timer,在兩個(gè) Mode 中都能得到回調(diào),一種辦法就是將這個(gè) Timer 分別加入這兩個(gè) Mode。還有一種方式,就是將 Timer 加入到頂層的 RunLoop 的 commonModeItems 中。commonModeItems 被 RunLoop 自動(dòng)更新到所有具有 Common 屬性的 Mode 里去。

CFRunLoop 對(duì)外暴露的管理 Mode 接口只有下面 2 個(gè):

    CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
    CFRunLoopRunInMode(CFStringRef modeName, ...);

Mode 暴露的管理 mode item 的接口有下面幾個(gè):

    CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
    CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
    CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
    CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
    CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
    CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);

你只能通過(guò) mode name 來(lái)操作內(nèi)部的 mode,當(dāng)你傳入一個(gè)新的 mode name 但 RunLoop 內(nèi)部沒(méi)有對(duì)應(yīng) mode 時(shí),RunLoop會(huì)自動(dòng)幫你創(chuàng)建對(duì)應(yīng)的 CFRunLoopModeRef。對(duì)于一個(gè) RunLoop 來(lái)說(shuō),其內(nèi)部的 mode 只能增加不能刪除。

蘋(píng)果公開(kāi)提供的 Mode 有兩個(gè),你可以用這兩個(gè) Mode Name 來(lái)操作其對(duì)應(yīng)的 Mode:

  • kCFRunLoopDefaultMode (NSDefaultRunLoopMode)
  • UITrackingRunLoopMode

同時(shí)蘋(píng)果還提供了一個(gè)操作 Common 標(biāo)記的字符串:kCFRunLoopCommonModes (NSRunLoopCommonModes),你可以用這個(gè)字符串來(lái)操作 Common Items,或標(biāo)記一個(gè) Mode 為 Common。使用時(shí)注意區(qū)分這個(gè)字符串和其他 mode name。


更多:iOS面試題合集

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容