一、RunLoop 的概念
一般來講,一個線程一次只能執(zhí)行一個任務(wù),執(zhí)行完成后線程就會退出。如果我們需要一個機(jī)制,讓線程能隨時處理事件但并不退出,通常的代碼邏輯是這樣的:
- function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
RunLoop實(shí)際上是一個對象,這個對象在循環(huán)中用來處理程序運(yùn)行過程中出現(xiàn)的各種事件(比如說觸摸事件、UI刷新事件、定時器事件、Selector事件),從而保持程序的持續(xù)運(yùn)行;而且在沒有事件處理的時候,會進(jìn)入睡眠模式,從而節(jié)省CPU資源,提高程序性能。
二、RunLoop基本作用
保持程序持續(xù)運(yùn)行
程序一啟動就會開一個主線程,主線程一開起來就會跑一個主線程對應(yīng)的RunLoop,RunLoop保證主線程不會被銷毀,也就保證了程序的持續(xù)運(yùn)行。處理App中的各種事件
比如:觸摸事件,定時器事件,Selector事件等。節(jié)省CPU資源,提高程序性能
程序運(yùn)行起來時,當(dāng)什么操作都沒有做的時候,RunLoop就告訴CUP,現(xiàn)在沒有事情做,我要去休息,這時CUP就會將其資源釋放出來去做其他的事情,當(dāng)有事情做的時候RunLoop就會立馬起來去做事情。
我們先通過API內(nèi)一張圖片來簡單看一下RunLoop內(nèi)部運(yùn)行原理:

通過上圖可以看出,RunLoop在跑圈過程中,當(dāng)接收到Input sources 或者 Timer sources時就會交給對應(yīng)的處理方去處理。當(dāng)沒有事件消息傳入的時候,RunLoop就休息了。
三、RunLoop 與線程的關(guān)系
- 每個線程,包括程序的主線程都有與之相應(yīng)的RunLoop對象。但主線程默認(rèn)自動創(chuàng)建好了RunLoop對象
- 對其它線程來說,RunLoop默認(rèn)是沒有創(chuàng)建的,如果你需要更多的線程交互則可以手動配置和啟動,如果線程只是去執(zhí)行一個長時間的已確定的任務(wù)則不需要。
- 蘋果不允許直接創(chuàng)建 RunLoop,它只提供了兩個自動獲取的函數(shù):CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 這兩個函數(shù)內(nèi)部的邏輯大概是下面這樣:
/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 訪問 loopsDic 時的鎖
static CFSpinLock_t loopsLock;
/// 獲取一個 pthread 對應(yīng)的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次進(jìn)入時,初始化全局Dic,并先為主線程創(chuàng)建一個 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
/// 直接從 Dictionary 里獲取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
/// 取不到時,創(chuàng)建一個
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
/// 注冊一個回調(diào),當(dāng)線程銷毀時,順便也銷毀其對應(yīng)的 RunLoop。
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
return loop;
}
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}
從上面的代碼可以看出,線程和 RunLoop 之間是一一對應(yīng)的,其關(guān)系是保存在一個全局的 Dictionary 里。線程剛創(chuàng)建時并沒有 RunLoop,如果你不主動獲取,那它一直都不會有。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時,RunLoop 的銷毀是發(fā)生在線程結(jié)束時,你只能在一個線程的內(nèi)部獲取其 RunLoop(主線程除外)。
四、RunLoop相關(guān)類
Core Foundation中關(guān)于RunLoop的5個類:
CFRunLoopRef //獲得當(dāng)前RunLoop和主RunLoop
CFRunLoopModeRef //運(yùn)行模式,只能選擇一種,在不同模式中做不同的操作
CFRunLoopSourceRef //事件源,輸入源
CFRunLoopTimerRef //定時器時間
CFRunLoopObserverRef //觀察者
其中 CFRunLoopModeRef 類并沒有對外暴露,只是通過 CFRunLoopRef 的接口進(jìn)行了封裝。他們的關(guān)系如下:

- CFRunLoopModeRef 一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer。每次調(diào)用 RunLoop 的主函數(shù)時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode。如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進(jìn)入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。
Cocoa和Core Foundation定義的標(biāo)準(zhǔn)模式,其中常見的有1.2兩種:
| Mode | name | Description |
|---|---|---|
| Default | NSDefaultRunLoopMode (Cocoa)、kCFRunLoopDefaultMode (Core Foundation) | 默認(rèn)模式用于大多數(shù)操作。大多數(shù)情況下,您應(yīng)該使用此模式啟動運(yùn)行循環(huán)并配置輸入源。 |
| Event tracking | NSEventTrackingRunLoopMode (Cocoa) | 此模式來限制鼠標(biāo)拖動循環(huán)和其他用戶界面跟蹤循環(huán)期間的傳入事件。 |
| Connection | NSConnectionReplyMode (Cocoa) | Cocoa將此模式與NSConnection對象結(jié)合使用來監(jiān)視響應(yīng)。應(yīng)該很少需要使用這種模式。 |
| Modal | NSModalPanelRunLoopMode (Cocoa) | Cocoa使用此模式來標(biāo)識用于模態(tài)面板的事件。 |
| Common modes | NSRunLoopCommonModes (Cocoa)、kCFRunLoopCommonModes (Core Foundation) | 這是一組可配置的常用模式。將輸入源與此模式關(guān)聯(lián)也將其與組中的每個模式關(guān)聯(lián)。對于Cocoa應(yīng)用程序,默認(rèn)情況下,這個集合包括默認(rèn)模式、模式和事件跟蹤模式。Core Foundation最初只包含默認(rèn)模式。您可以使用CFRunLoopAddCommonMode函數(shù)向集合添加自定義模式。 |
| -- | GSEventReceiveRunLoopMode | 用來接受系統(tǒng)事件,內(nèi)部的Run Loop Mode。 |
系統(tǒng)默認(rèn)注冊了5個ModekCFRunLoopDefaultMode、UITrackingRunLoopMode、GSEventReceiveRunLoopMode、kCFRunLoopCommonModes、UIInitializationRunLoopMode。通過CFRunLoopCopyAllModes()獲取能獲取到前四種mode。
CFRunLoopSourceRef 是事件產(chǎn)生的地方。Source有兩個版本:Source0 和 Source1。
? Source0 只包含了一個回調(diào)(函數(shù)指針),它并不能主動觸發(fā)事件。使用時,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標(biāo)記為待處理,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。
? Source1 包含了一個 mach_port 和一個回調(diào)(函數(shù)指針),被用于通過內(nèi)核和其他線程相互發(fā)送消息。這種 Source 能主動喚醒 RunLoop 的線程,其原理在下面會講到。CFRunLoopTimerRef 是基于時間的觸發(fā)器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一個時間長度和一個回調(diào)(函數(shù)指針)。當(dāng)其加入到 RunLoop 時,RunLoop會注冊對應(yīng)的時間點(diǎn),當(dāng)時間點(diǎn)到時,RunLoop會被喚醒以執(zhí)行那個回調(diào)。
CFRunLoopObserverRef 是觀察者,每個 Observer 都包含了一個回調(diào)(函數(shù)指針),當(dāng) RunLoop 的狀態(tài)發(fā)生變化時,觀察者就能通過回調(diào)接受到這個變化。可以觀測的時間點(diǎn)有以下幾個:
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,一個 item 可以被同時加入多個 mode。但一個 item 被重復(fù)加入同一個 mode 時是不會有效果的。如果一個 mode 中一個 item 都沒有,則 RunLoop 會直接退出,不進(jìn)入循環(huán)。
? Source0處理觸摸事件、performSelector:onThread
? Source1處理基于Port的線程間通信、系統(tǒng)事件捕捉
? Timers處理NSTimer、performSelector:withObject:afterDelay:
? Observers用于監(jiān)聽RunLoop的狀態(tài)、UI刷新「BeforeWaiting」Autorelease pool「BeforeWaiting」
五、RunLoop處理邏輯
根據(jù)蘋果在文檔里的說明,RunLoop 內(nèi)部的邏輯大致如下:

圖片上在處理source0之前會處理blocks,處理完source0之后還會判斷一下是否有需要處理的blocks,實(shí)際上 RunLoop 就是這樣一個函數(shù),其內(nèi)部是一個 do-while 循環(huán)。當(dāng)你調(diào)用 CFRunLoopRun() 時,線程就會一直停留在這個循環(huán)里;直到超時或被手動停止,該函數(shù)才會返回。
其內(nèi)部代碼整理如下:
/// 用DefaultMode啟動
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode啟動,允許設(shè)置RunLoop超時時間
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找到對應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里沒有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),直接處理這個 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)入休眠, 直到被下面某一個事件喚醒。
/// ? 一個基于 port 的Source 的事件。
/// ? 一個 Timer 到時間了
/// ? RunLoop 自身的超時時間到了
/// ? 被其他什么調(diào)用者手動喚醒
__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 如果一個 Timer 到時間了,觸發(fā)這個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 如果一個 Source1 (基于port) 發(fā)出事件了,處理這個事件
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ù)說處理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出傳入?yún)?shù)標(biāo)記的超時時間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部調(diào)用者強(qiáng)制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一個都沒有了
retVal = kCFRunLoopRunFinished;
}
/// 如果沒超時,mode里沒空,loop也沒被停止,那繼續(xù)loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即將退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
參考:
ibireme
李峰峰
CFRunLoopRef