一般來講,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完成后線程就會(huì)退出。如果我們需要一個(gè)機(jī)制,讓線程能隨時(shí)處理事件但并不退出,通常的代碼邏輯是這樣的:
do {
//獲取消息
//處理消息
} while (消息 != 退出)
這種模型通常被稱作 Event Loop 。 實(shí)現(xiàn)這種模型的關(guān)鍵點(diǎn)在于:如何管理事件/消息,如何讓線程在沒有處理消息時(shí)休眠以避免資源占用、在有消息到來時(shí)立刻被喚醒。
所以,RunLoop 實(shí)際上就是一個(gè)對象,這個(gè)對象管理了其需要處理的事件和消息,并提供了一個(gè)入口函數(shù)來執(zhí)行上面 Event Loop 的邏輯。線程執(zhí)行了這個(gè)函數(shù)后,就會(huì)一直處于這個(gè)函數(shù)內(nèi)部 “接受消息->等待->處理” 的循環(huán)中,直到這個(gè)循環(huán)結(jié)束(比如傳入 quit 的消息),函數(shù)返回。
以iOS 程序的 main 函數(shù)為例:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"main start");
int re = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"main stop");
return re;
}
}
main 函數(shù)是程序的入口,那么為什么程序執(zhí)行完畢后沒有退出呢?因?yàn)?RunLoop,使線程循環(huán),能夠隨時(shí)處理事件但并不退出。上面代碼中 UIApplicationMain()方法在這里不僅完成了初始化我們的程序并設(shè)置程序 Delegate 的任務(wù),而且隨之開啟了主線程的 RunLoop,開始接受處理事件。這樣我們的應(yīng)用就可以在無人操作的時(shí)候休息,需要讓它干活的時(shí)候又能立馬響應(yīng)。
OSX/iOS 系統(tǒng)中,提供了兩個(gè)這樣的對象: NSRunLoop 和 CFRunLoopRef。
CFRunLoopRef是在 CoreFoundation`` 框架內(nèi)的,它提供了純 C 函數(shù)的 API,所有這些 API 都是線程安全的。NSRunLoop是基于CFRunLoopRef``` 的封裝,提供了面向?qū)ο蟮?API,但是這些 API 不是線程安全的。
CFRunLoopRef的代碼是開源的,可以點(diǎn)擊下載到整個(gè) CoreFoundation 的源碼來查看。http://opensource.apple.com/tarballs/CF/
RunLoop大致內(nèi)部結(jié)構(gòu):
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
RunLoop 機(jī)制關(guān)系圖總覽:

RunLoop與線程的關(guān)系
蘋果不允許直接創(chuàng)建 RunLoop,提供了兩個(gè)獲取函數(shù),CFRunLoopRef 的獲取方法為CFRunLoopGetMain(), CFRunLoopGetCurrent()。NSRunLoop 的獲取方法是 currentRunLoop,mainRunLoop。NSRunLoop 對象可以通過 getCFRunLoop 方法獲得 CFRunLoopRef 對象。CFRunLoopRef 的內(nèi)部邏輯如下:
/// 全局的 Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 訪問 loopsDic 時(shí)的鎖
static CFSpinLock_t loopsLock;
/// 獲取一個(gè) pthread 對應(yīng)的 Run Loop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次進(jìn)入時(shí),初始化全局Dic,并先為主線程創(chuàng)建一個(gè) Run Loop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
/// 直接從 Dictionary 里獲取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
/// 取不到時(shí),創(chuàng)建一個(gè)
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
/// 注冊一個(gè)回調(diào),當(dāng)線程銷毀時(shí),順便也銷毀其對應(yīng)的 Run Loop。
_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)系圖中 CFRunLoop 和 Thread 連線中的兩個(gè)1的意義),其關(guān)系是保存在一個(gè)全局的 Dictionary 里。線程剛創(chuàng)建時(shí)并沒有 RunLoop,如果你不主動(dòng)獲取,那它一直都不會(huì)有,所以一個(gè)子線程,你想要它有 RunLoop 就必須在該線程內(nèi)調(diào)用 NSRunLoop *runLoop =[NSRunLoop currentRunLoop]。如果你想啟動(dòng)這個(gè) RunLoop,則要繼續(xù)調(diào)用[runLoop run]。但是注意,一般不需要開啟子線程的 runLoop,因?yàn)檫@會(huì)讓子線程一直存在,不會(huì)回收。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí),RunLoop 的銷毀是發(fā)生在線程結(jié)束時(shí)。你只能在一個(gè)線程的內(nèi)部獲取其 RunLoop(主線程除外)。
RunLoop主要組成
在 CoreFoundation 里面關(guān)于 RunLoop 有5個(gè)類:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
他們之間的關(guān)系如下圖:
一個(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
CFRunLoopMode 結(jié)構(gòu)大致如下:
struct __CFRunLoopMode {
CFStringRef _name; // mode名稱
CFMutableSetRef _sources0; // sources0
CFMutableSetRef _sources1; // sources1
CFMutableArrayRef _observers; // 通知
CFMutableArrayRef _timers; // 定時(shí)器
__CFPortSet _portSet; // 保存所有需要監(jiān)聽的port,比如 _wakeUpPort,_timerPort都保存在這個(gè)數(shù)組中
};
一個(gè)CFRunLoopMode對象有一個(gè)name,若干source0、source1、timer、observer和若干port,事件都是由Mode在管理,而RunLoop管理Mode。
Runloop中有個(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)用場景舉例:主線程的 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)操作。
如果需要一個(gè)Timer,在兩個(gè) Mode中都能得到回調(diào),一種辦法就是將這個(gè)Timer分別加入這兩個(gè)Mode。還有一種方式,就是將 Timer 加入到頂層的 RunLoop 的 commonModeItems 中。commonModeItems 被 RunLoop 自動(dòng)更新到所有具有Common屬性的 Mode 里去。
CFRunLoopSource
Source0 :
處理App內(nèi)部事件、App自己負(fù)責(zé)管理(觸發(fā)),如UIEvent。source0是非基于Port的。只包含了一個(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 :由RunLoop和內(nèi)核管理,Mach port驅(qū)動(dòng),如CFMachPort、CFMessagePort。
包含了一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針),被用于通過內(nèi)核和其他線程相互發(fā)送消息。這種 Source 能主動(dòng)喚醒 RunLoop 的線程。
CFRunLoopTimer
是基于時(shí)間的觸發(fā)器,基本上說的就是NSTimer,它受RunLoop的Mode影響,當(dāng)其加入到 RunLoop 時(shí),RunLoop會(huì)注冊對應(yīng)的時(shí)間點(diǎn),當(dāng)時(shí)間點(diǎn)到時(shí),RunLoop會(huì)被喚醒以執(zhí)行那個(gè)回調(diào)。如果線程阻塞或者不在這個(gè)Mode下,觸發(fā)點(diǎn)將不會(huì)執(zhí)行,一直等到下一個(gè)周期時(shí)間點(diǎn)觸發(fā)。
CFRunLoopObserver
CFRunLoopObserverRef是觀察者,每個(gè) Observer 都包含了一個(gè)回調(diào)(函數(shù)指針),當(dāng) RunLoop 的狀態(tài)發(fā)生變化時(shí),觀察者就能通過回調(diào)接受到這個(gè)變化??梢杂^測的時(shí)間點(diǎn)有以下幾個(gè):
kCFRunLoopEntry = (1 << 0), // 即將進(jìn)入Loop
kCFRunLoopBeforeTimers = (1 << 1), // 即將處理 Timer
kCFRunLoopBeforeSources = (1 << 2), // 即將處理 Source
kCFRunLoopBeforeWaiting = (1 << 5), // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1 << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1 << 7), // 即將退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU // 包含上面所有狀態(tài)
RunLoop 的內(nèi)部邏輯
RunLoop 內(nèi)部的邏輯大致如下:

其內(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找到對應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里沒有source/timer, 直接返回。
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ā) Source 回調(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ù)說處理完事件就返回。
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一個(gè)都沒有了
retVal = kCFRunLoopRunFinished;
}
/// 如果沒超時(shí),mode里沒空,loop也沒被停止,那繼續(xù)loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即將退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
可以看到,實(shí)際上 RunLoop 就是這樣一個(gè)函數(shù),其內(nèi)部是一個(gè) do-while 循環(huán)。當(dāng)你調(diào)用 CFRunLoopRun() 時(shí),線程就會(huì)一直停留在這個(gè)循環(huán)里;直到超時(shí)或被手動(dòng)停止,該函數(shù)才會(huì)返回。
應(yīng)用實(shí)例
AutoreleasePool
App啟動(dòng)后,蘋果在主線程 RunLoop 里注冊了兩個(gè) Observer。
第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop),其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush()創(chuàng)建自動(dòng)釋放池。其 order 是-2147483647,優(yōu)先級最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。
第二個(gè) Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop()和 _objc_autoreleasePoolPush()釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop) 時(shí)調(diào)用_objc_autoreleasePoolPop() 來釋放自動(dòng)釋放池。這個(gè) Observer 的 order 是 2147483647,優(yōu)先級最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。
在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)、Timer回調(diào)內(nèi)的。這些回調(diào)會(huì)被 RunLoop創(chuàng)建好的 AutoreleasePool 環(huán)繞著,所以不會(huì)出現(xiàn)內(nèi)存泄漏。
PerformSelecter
當(dāng)調(diào)用 NSObject 的performSelecter:afterDelay:后,實(shí)際上其內(nèi)部會(huì)創(chuàng)建一個(gè) Timer 并添加到當(dāng)前線程的 RunLoop 中。所以如果當(dāng)前線程沒有 RunLoop,則這個(gè)方法會(huì)失效。
當(dāng)調(diào)用 performSelector:onThread: 時(shí),實(shí)際上其會(huì)創(chuàng)建一個(gè) Timer加到對應(yīng)的線程去,同樣的,如果對應(yīng)線程沒有RunLoop 該方法也會(huì)失效。
