RunLoop 是什么?
RunLoop 是和線程緊密相關(guān)的一個(gè)基礎(chǔ)組件。顧名思議就是循環(huán)運(yùn)行。按照 OC 的思路,RunLoop 其實(shí)就是一個(gè)對(duì)象,這個(gè)對(duì)象管理了其需要處理的事件和消息并提供一個(gè)入口函數(shù)來(lái)循環(huán)執(zhí)行事件。平常,一般的 while 循環(huán)會(huì)讓 CPU 處于忙等狀態(tài),而 RunLoop 則是一種“閑等”,當(dāng)沒(méi)有事件時(shí),RunLoop 會(huì)進(jìn)入休眠狀態(tài),有事件發(fā)生時(shí), RunLoop 會(huì)找對(duì)應(yīng)的 Handler 處理事件。
邏輯代碼如下:
+ (void) loop {
[self initialize];
do {
id message = [self get_next_Message];
[self process_message:message];
} while (message != quit);
}
RunLoop 可以保持程序的正常運(yùn)行,可以處理 APP 的各種事件(比如觸摸、定時(shí)器等)。同時(shí)也節(jié)省了 CPU 的資源、 提高性能。
OS X/iOS 系統(tǒng)中,提供了兩個(gè)對(duì)象:
- CFRunLoopRef : 在 Core Foundation 框架內(nèi),提供了純 C函數(shù)且線程安全的 API
- NSRunLoop: 基于 CFRunLoop 的封裝,提供了面向?qū)ο蟮?API,但這些線程是不安全的。
這兩類 API 都可以訪問(wèn)和使用 RunLoop,但相對(duì)來(lái)說(shuō),CFRunLoopRef 的性能更高。
首先,看一下官方 RunLoop 結(jié)構(gòu)圖(下圖的Input Source Port 對(duì)應(yīng)的是 Source1)

注意:圖中出現(xiàn)的 Input Source 和 Timer Source 都是 RunLoop 事件的來(lái)源。但是不同之處在于所有的 Timer 都共用一個(gè)端口 "Mode Timer Port" ,而每個(gè)Source1 都有不同的對(duì)應(yīng)端口。
RunLoop 與線程
CFRunLoop 是基于 pthread 來(lái)管理的。每個(gè)線程都有一個(gè)對(duì)應(yīng)的 RunLoop 對(duì)象。它們之間的關(guān)系保存在一個(gè)全局的 Dictionary 中。
蘋(píng)果不允許直接創(chuàng)建 RunLoop,它提供了 CFRunLoopGetMain()和 CFRunLoopGetCurrent() 這兩個(gè) API 來(lái)獲取 RunLoop 對(duì)象。
主線程的 RunLoop 會(huì)在應(yīng)用啟動(dòng)的時(shí)候完成啟動(dòng),其他線程的 RunLoop 默認(rèn)并不會(huì)開(kāi)啟,需要我們主動(dòng)獲取。
RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí),銷毀是在線程結(jié)束。并且你只能在一個(gè)線程的內(nèi)部獲取其 RunLoop(主線程外)。
RunLoop 相關(guān)類
在 Core Foundation 里有5個(gè)關(guān)于 RunLoop 的類:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRed
其中,CFRunLoopModeRef 沒(méi)有對(duì)外暴露,只是通過(guò) CFRunLoopRef 的接口進(jìn)行了封裝。它們之間的關(guān)系如下圖:

一個(gè) RunLoop 包含若干個(gè) Mode,每個(gè) Mode 又包含若干個(gè) Source、Observer、Timer。每次調(diào)用 RunLoop 的主函數(shù) __CFRunLoopRun() 時(shí)必須且只能指定一個(gè) Mode,這個(gè) Mode 就被稱為 CurrentMode;如果需要切換 Mode,只能退出 Loop,在重新指定一個(gè) Mode 進(jìn)入。
CFRunLoopSourceRef 是事件產(chǎn)生的地方。有兩種Source:
- Source0: 僅包含一個(gè)回調(diào)(函數(shù)指針),不能主動(dòng)觸發(fā)事件;使用時(shí),你需要先調(diào)用
CFRunLoopSourceSignal(source),將這個(gè) Source 標(biāo)記為待處理,然后在調(diào)用 CFRunLoopWakeUp(runloop)來(lái)喚醒 RunLoop,讓其處理這個(gè)事件。它負(fù)責(zé) APP 內(nèi)部事件,由 APP 負(fù)責(zé)管理觸發(fā),例如 UITouch 事件。 - Source1: 包含一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針),被用于通過(guò)內(nèi)核和其他線程互相發(fā)送消息。能夠主動(dòng)喚醒 RunLoop 的線程。它由操作系統(tǒng)內(nèi)核進(jìn)行管理,例如 CFMessagePort 消息。
CFRunLoopTimeRef 是基于時(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 是觀察者,每個(gè) Observer 都包含了一個(gè)回調(diào)(函數(shù)指針),當(dāng) RunLoop 的狀態(tài)發(fā)生變化時(shí),觀察者就能通過(guò)回調(diào)接受到這個(gè)變化。我們可以觀測(cè)的狀態(tài)有:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
}
上面的 Source/Timer/Observer 被統(tǒ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)。
RunLoop 的 Mode
首先,我們先來(lái)了解一下 CFRunLoopMode 和 CFRunLoop 的結(jié)構(gòu):
struct __CFRunLoopMode {
CFString _name; // mode name
CFMutableSetRef _sources0;
CFMutableSetRef -sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
···
};
struct __CFRunLoop {
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current RunLoop Mode
CFMutableSetRef _modes;
}
CommonModes
CommonModes: 一個(gè) Mode 可以通過(guò)將其 ModeName 添加到 RunLoop 的 "commonModes" 中,從而將自己標(biāo)記為"Common" 屬性。每當(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),當(dāng)你滑動(dòng) TableView 時(shí),Mode 就會(huì)切換成為 TrackingRunLoopMode ,這個(gè)時(shí)候 Timer 就不會(huì)回調(diào),同時(shí)也不會(huì)影響到滑動(dòng)操作。
有時(shí)候你需要一個(gè) Timer在兩個(gè) Mode 中都可以得到回調(diào),方法一就是將這個(gè) Timer 分別加入到這兩個(gè) Mode;方法二就是將 Timer 加入到頂層的 RunLoop 的 "commonModeItems" 中。
Mode 相關(guān)的接口
CFRunLoop 對(duì)外暴露管理 Mode 接口:
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFString modeName);
CFRunLoopRunInMode(CFStringRef modeName,...);
Mode 對(duì)外暴露的管理 item 的接口:
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ò) modeName 來(lái)操作內(nèi)部的 Mode,如果你傳入一個(gè)新的 modeName 。但是 RunLoop 內(nèi)部不存在時(shí),RunLoop 會(huì)自動(dòng)創(chuàng)建一個(gè)對(duì)應(yīng)的 CFRunLoopModeRef;對(duì)于一個(gè) RunLoop 來(lái)說(shuō),其內(nèi)部的 mode 只能增加不能刪除。
我們?cè)谏厦嫣岬搅藘煞N預(yù)置的 Mode,當(dāng)我們切換到對(duì)應(yīng)的 Mode 時(shí),我們只需要傳入對(duì)應(yīng)的名稱即可。然而,還存在
KCFRunLoopCommonModes(NSRunloopCommonModes),它是一種組合模式,在 iOS 默認(rèn)包含了NSDefaultRunLoopMode和UITrackingRunLoopMode(注意:并不是說(shuō)Runloop會(huì)運(yùn)行在 kCFRunLoopCommonModes 這種模式下,而是相當(dāng)于分別注冊(cè)了 NSDefaultRunLoopMode 和 UITrackingRunLoopMode。當(dāng)然你也可以通過(guò)調(diào)用CFRunLoopAddCommonMode()方法將自定義Mode放到 kCFRunLoopCommonModes 組合)。
注意:
我們常常還會(huì)碰到一些系統(tǒng)框架自定義Mode,例如Foundation中NSConnectionReplyMode。還有一些系統(tǒng)私有Mode,例如:GSEventReceiveRunLoopMode接受系統(tǒng)事件,UIInitializationRunLoopMode App啟動(dòng)過(guò)程中初始化Mode。
系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:
- kCFRunLoopDefaultMode: APP 默認(rèn)的Mode,通常主線程是在這個(gè) Mode 下運(yùn)行。
- UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)不受其他 Mode 影響。
- UIInitializationRunLoopMode: 在剛啟動(dòng) APP 時(shí)進(jìn)入的第一個(gè) Mode,啟動(dòng)完之后就不會(huì)在使用。
- GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到。
- kCFRunLoopCommonModes: 占位 Mode,沒(méi)有實(shí)際作用。
當(dāng) RunLoop 進(jìn)行回調(diào)時(shí),一般都是通過(guò)一個(gè)很長(zhǎng)的函數(shù)調(diào)用出去(call out),當(dāng)你在你的代碼中斷點(diǎn)調(diào)試時(shí),通常能在調(diào)用棧上看到這些函數(shù)。
RunLoop 的內(nèi)部邏輯
首先,通過(guò)一張圖,來(lái)了解一下 RunLoop 的運(yùn)行流程:

內(nèi)部代碼整理如下;
// 用DefaultMode啟動(dòng)
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
// 用指定的Mode啟動(dòng),允許設(shè)置RunLoop超時(shí)時(shí)間
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
// RunLoop的實(shí)現(xiàn)
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
// 首先根據(jù)modeName找到對(duì)應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
// 如果mode里沒(méi)有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
// 1. 通知 Observers: RunLoop 即將進(jìn)入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
// 內(nèi)部函數(shù),進(jìn)入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
// 通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒。
// ? 一個(gè)基于 port 的Source 的事件。
// ? 一個(gè) Timer 到時(shí)間了
// ? RunLoop 自身的超時(shí)時(shí)間到了
// ? 被其他什么調(diào)用者手動(dòng)喚醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
// 收到消息,處理消息。
handle_msg:
// 9.1 如果一個(gè) Timer 到時(shí)間了,觸發(fā)這個(gè)Timer的回調(diào)。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
// 9.2 如果有dispatch到main_queue的block,執(zhí)行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
// 9.3 如果一個(gè) Source1 (基于port) 發(fā)出事件了,處理這個(gè)事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
// 執(zhí)行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
// 進(jìn)入loop時(shí)參數(shù)說(shuō)處理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
// 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
// 被外部調(diào)用者強(qiáng)制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
// source/timer/observer一個(gè)都沒(méi)有了
retVal = kCFRunLoopRunFinished;
}
// 如果沒(méi)超時(shí),mode里沒(méi)空,loop也沒(méi)被停止,那繼續(xù)loop。
} while (retVal == 0);
}
// 10. 通知 Observers: RunLoop 即將退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
從源碼很容易看出,Runloop 總是運(yùn)行在某種特定的 CFRunLoopModeRef 下(每次運(yùn)行__CFRunLoopRun()函數(shù)時(shí)必須指定 Mode )。它就是一個(gè)帶有一個(gè) do-while 循環(huán)的一個(gè)函數(shù)。當(dāng)你調(diào)用 CFRunLoopRun() 時(shí),線程就會(huì)一直停留在這個(gè)循環(huán)里,只有超時(shí)、被手動(dòng)停止或者 item 為空時(shí),該函數(shù)才會(huì)返回。
RunLoop 的底層實(shí)現(xiàn)
其實(shí),對(duì)于 RunLoop 而言,最核心的就是保證線程在沒(méi)有消息時(shí)休眠從而避免占用系統(tǒng)資源,有消息傳入時(shí)能夠及時(shí)喚醒。而這個(gè)機(jī)制完全依靠系統(tǒng)內(nèi)核來(lái)完成。
從上一節(jié)的源碼中可以看到,RunLoop 的核心是基于 mach port 的,其進(jìn)入休眠時(shí)調(diào)用的函數(shù)是mach_msg()。RunLoop 調(diào)用這個(gè)函數(shù)去接受消息,如果沒(méi)有外部發(fā)來(lái)的 port消息,內(nèi)核會(huì)一直將線程置于等待狀態(tài)。
RunLoop 的應(yīng)用
定時(shí)器
開(kāi)頭就提到的Timer Source 作為事件源,它的上層對(duì)應(yīng)的就是 NSTimer(CFRunLoopTimerRef)。NSTimer 定時(shí)器的觸發(fā)基于 RunLoop 運(yùn)行,使用 NSTimer 之前必須注冊(cè)到 RunLoop,但是 RunLoop 為了節(jié)省資源并不會(huì)在非常準(zhǔn)確的時(shí)間點(diǎn)調(diào)用定時(shí)器,如果一個(gè)任務(wù)執(zhí)行時(shí)間較長(zhǎng),那么當(dāng)錯(cuò)過(guò)一個(gè)時(shí)間點(diǎn)后只能等待下一個(gè)時(shí)間點(diǎn)執(zhí)行,并不會(huì)延后執(zhí)行(NSTimer 提供了一個(gè) tolerance 屬性用于設(shè)置寬容度,可以通過(guò)設(shè)置此屬性來(lái)盡可能的使 NSTimer 準(zhǔn)確)。
CADisplayLink 是一個(gè)執(zhí)行頻率(fps)和屏幕刷新相同的定時(shí)器,可以修改preferredFramesPerSecond改變刷新頻率;它也需要加入到RunLoop才能執(zhí)行。與NSTimer類似,CADisplayLink同樣是基于CFRunloopTimerRef實(shí)現(xiàn),底層使用mk_timer(可以比較加入到RunLoop前后RunLoop中timer的變化)。和NSTimer相比它精度更高(盡管NSTimer也可以修改精度),不過(guò)和NStimer類似的是如果遇到大任務(wù)它仍然存在丟幀現(xiàn)象。通常情況下CADisaplayLink用于構(gòu)建幀動(dòng)畫(huà),看起來(lái)相對(duì)更加流暢,而NSTimer則有更廣泛的用處。
AutoreleasePool
AutoreleasePool是另一個(gè)與RunLoop相關(guān)討論較多的話題。其實(shí)從RunLoop源代碼分析,AutoreleasePool與RunLoop并沒(méi)有直接的關(guān)系,之所以將兩個(gè)話題放到一起討論最主要的原因是因?yàn)樵?APP 啟動(dòng)后,蘋(píng)果在主線程 RunLoop 里注冊(cè)了兩個(gè) Observer 管理和維護(hù) AutorealeasePool,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入 Loop), 其回調(diào)內(nèi)會(huì)調(diào)用_objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池。它的優(yōu)先級(jí)最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。
第二個(gè) Observer 監(jiān)視了兩個(gè)事件: BeforeWaaiting(準(zhǔn)備進(jìn)入休眠)時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新的池;Exit(即將退出Loop)時(shí)調(diào)用_objc_autoreleasePoolPop()來(lái)釋放自動(dòng)釋放池。它的優(yōu)先級(jí)最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。
主線程中的其他操作通常均在這個(gè) AutorelsePool 之內(nèi)(main函數(shù)),以盡可能減少內(nèi)存維護(hù)操作。
事件響應(yīng)
蘋(píng)果注冊(cè)了一個(gè) Source1(基于 mach port)用來(lái)接受系統(tǒng)事件,其回調(diào)函數(shù)為__IOHIDEventSystemClientQueueCallback()。
當(dāng)一個(gè)硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后,首先由 IOKit.framework 生成一個(gè) IOHIDEvent 事件并由 SpringBoard 接收。
SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸,加速,接近傳感器等幾種Event,隨后用 mach port 轉(zhuǎn)發(fā)給需要的 App 進(jìn)程。隨后蘋(píng)果注冊(cè)的那個(gè) Source1 就會(huì)觸發(fā)回調(diào),并調(diào)用 _UIApplicationHandleEventQueue() 進(jìn)行應(yīng)用內(nèi)部的分發(fā)。
_UIApplicationHandleEventQueue() 會(huì)把 IOHIDEvent 處理并包裝成 UIEvent 進(jìn)行處理或分發(fā),其中包括識(shí)別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給UIWindow等。通常事件比如 UIButton 點(diǎn)擊、 TouchesBegin/Move/End/Cancel 事件都是在這個(gè)回調(diào)中完成的。
手勢(shì)識(shí)別
當(dāng)上面的_UIApplicationHandleEventQueue() 識(shí)別了一個(gè)手勢(shì)時(shí),其首先會(huì)調(diào)用 Cancel 將當(dāng)前的 touchesBegin/Move/End 系列回調(diào)打斷。隨后系統(tǒng)將對(duì)應(yīng)的 UIGestureRecognizer 標(biāo)記為待處理。
蘋(píng)果注冊(cè)了一個(gè) Observer 監(jiān)測(cè) BeforeWaiting (Loop即將進(jìn)入休眠) 事件,這個(gè)Observer的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver(),其內(nèi)部會(huì)獲取所有剛被標(biāo)記為待處理的 GestureRecognizer,并執(zhí)行 GestureRecognizer 的回調(diào)。
當(dāng)有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時(shí),這個(gè)回調(diào)都會(huì)進(jìn)行相應(yīng)處理。
界面更新
打印App啟動(dòng)之后的主線程 RunLoop 可以發(fā)現(xiàn)另外一個(gè)callout為_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv的Observer,這個(gè)監(jiān)聽(tīng)專門負(fù)責(zé)UI變化后的更新,比如修改了frame、調(diào)整了UI層級(jí)(UIView/CALayer)或者手動(dòng)設(shè)置了setNeedsDisplay/setNeedsLayout之后就會(huì)將這些操作提交到全局容器。
這個(gè) Observers 監(jiān)聽(tīng)了主線程 RunLoop 的 BeforeWaiting(即將進(jìn)入休眠)和 Exit (即將退出 Loop)狀態(tài),一旦進(jìn)入到這兩種狀態(tài)則會(huì)遍歷所有的 UI 更新并提交進(jìn)行實(shí)際繪制更新。
該函數(shù)內(nèi)部的調(diào)用棧大致如下:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
QuartzCore:CA::Transaction::observer_callback:
CA::Transaction::commit();
CA::Context::commit_transaction();
CA::Layer::layout_and_display_if_needed();
CA::Layer::layout_if_needed();
[CALayer layoutSublayers];
[UIView layoutSubviews];
CA::Layer::display_if_needed();
[CALayer display];
[UIView drawRect];
通常情況下這種方式是完美的,因?yàn)槌讼到y(tǒng)的更新,還可以利用 setNeedsDisplay 等方法手動(dòng)觸發(fā)下一次 RunLoop 運(yùn)行的更新。但是如果當(dāng)前正在執(zhí)行大量的邏輯運(yùn)算可能UI的更新就會(huì)比較卡,因此facebook推出了AsyncDisplayKit來(lái)解決這個(gè)問(wèn)題。有關(guān) AsyncDiskplayKit ,后文會(huì)具體講到。
PerformSelecter
當(dāng)調(diào)用 NSObject 的 performSelecter: afterDelay:后,實(shí)際上其內(nèi)部會(huì)創(chuàng)建一個(gè) Timer 并添加到當(dāng)前線程的 RunLoop 中。如果當(dāng)前線程沒(méi)有 runloop ,該方法會(huì)隨之失效。
當(dāng)調(diào)用performSelector: onThread: 時(shí),會(huì)創(chuàng)建一個(gè) Timer 加到對(duì)應(yīng)的線程中去,同樣的,如果對(duì)應(yīng)的線程沒(méi)有 RunLoop 該方法也會(huì)失效。
關(guān)于 GCD
在 RunLoop 的源代碼中可以看到 GCD 的相關(guān)東西,但是它倆本質(zhì)是沒(méi)有直接關(guān)系。
當(dāng)調(diào)用dispatch_async(dispatch_get_main_queue(), block)時(shí),libDispatch 會(huì)向主線程的 RunLoop 發(fā)送消息,RunLoop會(huì)被喚醒,并從消息中取得這個(gè) block,并在回調(diào) __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() 里執(zhí)行這個(gè) block。但這個(gè)邏輯僅限于 dispatch 到主線程,dispatch 到其他線程仍然是由 libDispatch 處理的。
關(guān)于網(wǎng)絡(luò)請(qǐng)求
iOS 中,關(guān)于網(wǎng)絡(luò)請(qǐng)求的接口自下而上有如下幾層:
- CFSocket:最底層的接口,只負(fù)責(zé) socket 通信。
- CFNetwork: 基于 CFSocket 等接口的上層封裝。
- NSURLConnection:基于 CFNetwork 的更高層封裝,提供面向?qū)ο蟮慕涌?/li>
- NSURLSession 是 iOS7 中新增的接口,表面上是和 NSURLConnection 并列的,但底層仍然用到了 NSNRLConnection 的部分功能。
NSURLConnection 的工作過(guò)程
通常使用 NSURLConnection 時(shí),你會(huì)傳入一個(gè) Delegate,當(dāng)調(diào)用了 [connection start] 后,這個(gè) Delegate 就會(huì)不停的收到事件回調(diào)。實(shí)際上,start 這個(gè)函數(shù)的內(nèi)部會(huì)獲取 CurrentRunLoop,然后在其中的 DefaultMode 添加了4個(gè) Source0(需要手動(dòng)觸發(fā)的 Source)。
- CFHTTPCookieStorage: 用于處理 cookie
- CFMultiplexerSource: 負(fù)責(zé)各種 Delegate 回調(diào),并在回調(diào)中喚醒 Delegate 內(nèi)部的 RunLoop(通常是主線程)來(lái)執(zhí)行實(shí)際操作。
當(dāng)開(kāi)始網(wǎng)絡(luò)傳輸時(shí), NSURLConnection 會(huì)創(chuàng)建兩個(gè)新線程:com.apple.NSURLConnectionLoader 和 com.apple.CFSocket.private。其中 CFSocket 線程是處理底層 socket 連接的。NSURLConnectionLoader 這個(gè)線程內(nèi)部會(huì)使用 RunLoop 來(lái)接收底層 sokcet 的事件,并通過(guò)之前添加的 Source0 通知到上層的 Delegate。

NSURLConnectionLoader 中的 RunLoop 通過(guò)一些基于 mach port 的 Source 接收來(lái)自底層 CFSocket 的通知。當(dāng)收到通知后,其會(huì)在合適的時(shí)機(jī)向 CFMultiplexerSource 等 Source0 發(fā)送通知,同時(shí)喚醒 Delegate 線程的 RunLoop 來(lái)讓其處理這些通知。CFMultiplexerSource 會(huì)在 Delegate 線程的 RunLoop 對(duì) Delegate 執(zhí)行實(shí)際的回調(diào)。
具體實(shí)例舉例
AFNetworking 2.x
AF 2.x 基于NSURLConnection包裝的重要對(duì)象,由于iOS9-NSURLConnection已經(jīng)不能使用,AFNetworking在3.x版本中刪除了基于 NSURLConnection API的所有支持。
因此,我們要研究的就是 AFNetworking 2.x 。
AFURLConnectionOperation 這個(gè)類是基于 NSURLConnection 構(gòu)建的。AFNetworking 單獨(dú)創(chuàng)建了一個(gè)線程,并在這個(gè)線程中啟動(dòng)了一個(gè) RunLoop,在后臺(tái)接收 Delegate 回調(diào)。具體代碼如下:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
RunLoop 啟動(dòng)前內(nèi)部必須要有至少一個(gè) Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先創(chuàng)建了一個(gè)新的 NSMachport 添加進(jìn)去了。通常情況下,調(diào)用者需要持有這個(gè) NSMachPort(mach_port) 并在外部線程通過(guò)這個(gè) port 發(fā)送消息到 loop 內(nèi);此處添加的 port 只是為了讓 RunLoop 不退出。
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
當(dāng)需要這個(gè)后臺(tái)執(zhí)行任務(wù)時(shí), AFNetworking 通過(guò)調(diào)用 [NSObject performSelector: onThread:...] 將這個(gè)任務(wù)扔到了后臺(tái)線程的 RunLoop 中。
AsyncDisplayKit
AsyncDisplayKit 是 Facebook 推出的用于保持界面流暢性的框架,其原理大致如下:
UI 線程中的任務(wù)通常分為三類:排版、繪制、UI 對(duì)象操作,當(dāng)這些任務(wù)過(guò)于繁重的話就會(huì)導(dǎo)致界面卡頓。
排版通常包括計(jì)算視圖大小、計(jì)算文本高度、重新計(jì)算子視圖的排版等操作。
繪制一般有文本繪制(coreText)、圖片繪制(例如預(yù)先解壓)、元素繪制(Quartz)等操作。
UI 對(duì)象操作通常包括 UIView/CALayer 等 UI 對(duì)象的創(chuàng)建、設(shè)置屬性和銷毀。
其中前兩類操作可以通過(guò)各種方法放到后臺(tái)去執(zhí)行,而最后一項(xiàng)操作只能在主線程完成,并且有時(shí)后面的操作需要依賴前面操作的結(jié)果(TextView 創(chuàng)建時(shí)可能需要提前計(jì)算出文本的大小)。
AsyncDisplayKit 所做的就是盡量將能放入到后臺(tái)的任務(wù)放入后臺(tái),不能的則盡量推遲(例如視圖的創(chuàng)建、屬性的調(diào)整)。因此, AsyncDisplayKit 創(chuàng)建了一個(gè)名為 AsyDisplayNode 的對(duì)象,并在其內(nèi)部封裝了 UIView/CALayer,它具有和 UIView/CALayer 相似的屬性,例如frame、backgroundColor 等。所有這些屬性都可以在后臺(tái)線程更改,開(kāi)發(fā)者只可以通過(guò) Node 來(lái)操作其內(nèi)部的 UIView/CALayer .,這些就可以將排版和繪制放入了后臺(tái)線程。但是無(wú)論怎樣操作,但是屬性總需要在某個(gè)時(shí)刻同步到主線程的 UIView/CALayer 去。
AsyncDisplayKit 仿照 QuartzCore/UIKit 框架的模式,實(shí)現(xiàn)了一套類似的界面更新的機(jī)制:即在主線程的 RunLoop 中添加一個(gè) Observer,監(jiān)聽(tīng)了 kCFRunLoopBeforeWaiting 和 kCFRunLoopExit 事件,在收到回調(diào)時(shí),遍歷所有之前放入隊(duì)列的待處理的任務(wù),然后一一執(zhí)行。