在 CoreFoundation 里面關(guān)于 RunLoop 有 5 個(gè)類狈邑,分別對(duì)應(yīng)不同的概念:
CFRunLoopRef,對(duì)應(yīng) runloop蚤认。
CFRunLoopModeRef米苹,對(duì)應(yīng) runloop mode。CFRunLoopModeRef 類并沒有對(duì)外暴露砰琢,只是通過 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) 來喚醒 RunLoop澳骤,讓其處理這個(gè)事件。Source1 包含了一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針)澜薄,被用于通過內(nèi)核和其他線程相互發(fā)送消息为肮。這種 Source 能主動(dòng)喚醒 RunLoop 的線程。
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í)突琳,觀察者就能通過回調(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)稱為 mode item趟脂,一個(gè) item 可以被同時(shí)加入多個(gè) mode。但一個(gè) item 被重復(fù)加入同一個(gè) mode 時(shí)是不會(huì)有效果的例衍。如果一個(gè) mode 中一個(gè) item 都沒有昔期,則 RunLoop 會(huì)直接退出,不進(jìn)入循環(huán)佛玄。
這些概念的包含關(guān)系如下圖所示:
線程的運(yùn)行的過程中需要去處理不同情境的不同事件硼一,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 被稱作 CurrentMode。如果需要切換 Mode圈驼,只能退出 Loop,再重新指定一個(gè) Mode 進(jìn)入望几。這樣做主要是為了分隔開不同組的 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 屬性(通過將其 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)景舉例:主線程的 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 并沒有被添加到 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);
你只能通過 mode name 來操作內(nèi)部的 mode,當(dāng)你傳入一個(gè)新的 mode name 但 RunLoop 內(nèi)部沒有對(duì)應(yīng) mode 時(shí)程储,RunLoop會(huì)自動(dòng)幫你創(chuàng)建對(duì)應(yīng)的 CFRunLoopModeRef蹭沛。對(duì)于一個(gè) RunLoop 來說,其內(nèi)部的 mode 只能增加不能刪除章鲤。
蘋果公開提供的 Mode 有兩個(gè)摊灭,你可以用這兩個(gè) Mode Name 來操作其對(duì)應(yīng)的 Mode:
- kCFRunLoopDefaultMode (NSDefaultRunLoopMode)
- UITrackingRunLoopMode
同時(shí)蘋果還提供了一個(gè)操作 Common 標(biāo)記的字符串:kCFRunLoopCommonModes (NSRunLoopCommonModes),你可以用這個(gè)字符串來操作 Common Items败徊,或標(biāo)記一個(gè) Mode 為 Common帚呼。使用時(shí)注意區(qū)分這個(gè)字符串和其他 mode name。
更多:iOS面試題合集