通常我們開發(fā)iOS app時(shí)接觸到的是NSRunLoop,而NSRunLoop實(shí)際上是對(duì)蘋果的Core Foundation框架中CFRunLoop的封裝,這次我們直接通過官方文檔和Core Foundation源碼學(xué)習(xí)CFRunLoop。
Core Foundation是純C版本的實(shí)現(xiàn),蘋果已經(jīng)開源了Core Foundation的源碼,相關(guān)鏈接: 官方文檔 源碼下載
CFRunLoop
頭文件中的聲明
typedef struct __CFRunLoop * CFRunLoopRef;
__CFRunLoop的定義(只保留了主要屬性)
struct __CFRunLoop {
CFMutableSetRef _commonModes;//common mode集合,后邊會(huì)講
CFMutableSetRef _commonModeItems;//source集合
CFRunLoopModeRef _currentMode;//當(dāng)前生效的mode
CFMutableSetRef _modes;//當(dāng)前runloop的所有mode
struct _block_item *_blocks_head;//block鏈表頭
struct _block_item *_blocks_tail;//block鏈表尾部
};
可以看到,一個(gè)run loop對(duì)象包含的元素并不多,其中_commonModes、_commonModeItems、_currentMode、_modes都跟mode有關(guān),剩余兩個(gè)屬性是一個(gè)鏈表,用來保存block。
那么mode是什么,接下來看一看mode的定義
CFRunLoopMode
如下是CFRunLoopModeRef的定義:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name;//mode的名字
Boolean _stopped;//用于退出當(dāng)前mode的方法
//輸入源 source
CFMutableSetRef _sources0;//非port相關(guān)事件(如用戶點(diǎn)擊)
CFMutableSetRef _sources1;//port相關(guān)的事件(系統(tǒng)事件)
//監(jiān)聽者 observer
CFMutableArrayRef _observers;
CFIndex _observerMask;//監(jiān)聽者注冊(cè)的監(jiān)聽事件
//計(jì)時(shí)器 timer
CFMutableArrayRef _timers;
};
一個(gè)mode包含三種類型的對(duì)象,sources(CFRunLoopSource)、timers(CFRunLoopTimer)和observers(CFRunLoopObserver),要接受run loop的回調(diào),必須依賴這三種對(duì)象。他們和run loop以及mode之間的關(guān)系如下:

每一個(gè)線程對(duì)應(yīng)一個(gè)run loop對(duì)象,每一個(gè)run loop包含多個(gè)mode,每一個(gè)mode包含多個(gè)source、timer以及observer。
每個(gè)source、timer和observer必須添加到一個(gè)或多個(gè)mode中才能生效,但是run loop同時(shí)只能運(yùn)行一個(gè)mode,如果當(dāng)前運(yùn)行的mode不是添加的mode,則不會(huì)生效。
舉個(gè)
UIScrollView的例子主線程的run loop默認(rèn)運(yùn)行的是
NSDefaultRunLoopMode,而當(dāng)滑動(dòng)UIScrollView時(shí),主線程的run loop會(huì)切換到UITrackingRunLoopMode。如果在主線程的
NSDefaultRunLoopMode中加入了一個(gè)NSTimer,當(dāng)用戶滑動(dòng)UIScrollView的時(shí)候,主線程會(huì)將run loop切換到UITrackingRunLoopMode,這時(shí)這個(gè)NSTimer就不會(huì)生效。直到用戶停止滑動(dòng)時(shí),主線程將run loop切換回NSDefaultRunLoopMode,此時(shí)計(jì)時(shí)器才會(huì)生效。
系統(tǒng)定義了一些mode,常見的有
- 默認(rèn)mode:
NSDefaultRunLoopMode(Cocoa) /kCFRunLoopDefaultMode(Core Foundation) - 事件跟蹤:
UITrackingRunLoopMode(Cocoa) - Common Modes:
NSRunLoopCommonModes(Cocoa) /kCFRunLoopCommonModes(Core Foundation)
當(dāng)我們需要執(zhí)行一些優(yōu)先級(jí)較高的任務(wù)時(shí),也可以自定義mode,限制一些低優(yōu)先級(jí)的事件,保證高優(yōu)先級(jí)任務(wù)的執(zhí)行。
每一個(gè)Mode通過name區(qū)分,Core Foundation沒有對(duì)外暴露run loop mode的接口,使用者只需要關(guān)心mode的name即可,如NSDefaultRunLoopMode、kCFRunLoopDefaultMode和UITrackingRunLoopMode,他們都是string類型(可以通過toll-free bridge轉(zhuǎn)換)。
kCFRunLoopCommonModes
在__CFRunLoop的定義中,如下兩句定義了common modes相關(guān)的變量:
CFMutableSetRef _commonModes;//common mode集合
CFMutableSetRef _commonModeItems;//source/timer/observer的集合
Common modes不是一個(gè)mode,而是很多mode的集合。被添加到common modes中的mode,會(huì)存儲(chǔ)在_commonModes中,同時(shí)會(huì)將_commonModeItems中的元素添加到這個(gè)mode中,這樣_commonModeItems可以被_commonModes包含的每一個(gè)mode運(yùn)行時(shí)監(jiān)聽到。
從使用者的角度來說,當(dāng)添加了一個(gè)timer到NSRunLoopCommonModes中,無論當(dāng)前run loop運(yùn)行在哪一種mode下,只要這個(gè)mode在common modes集合中,這個(gè)NSTimer就會(huì)生效。
再拿上面
UIScrollView舉個(gè)例子在主線程中,
NSDefaultRunLoopMode和UITrackingRunLoopMode這兩個(gè)mode被系統(tǒng)加入了common modes中。這意味著,我們?nèi)绻麑?code>NSTimer加入到common modes中,也就是添加到kCFRunLoopCommonModes,此時(shí)無論用戶是否滑動(dòng)UIScrollView,這個(gè)NSTimer都會(huì)生效。
這里有兩種方式與common modes打交道,一個(gè)是添加自定義mode到common modes中,另一個(gè)是添加sources/timers/observers到kCFRunLoopCommonModes,來看看對(duì)應(yīng)的實(shí)現(xiàn)
1. CFRunLoopAddCommonMode
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
//判斷common modes是否已經(jīng)存在該mode
if (!CFSetContainsValue(rl->_commonModes, modeName)) {
//獲取common modes set
CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
//添加mode進(jìn)common modes set
CFSetAddValue(rl->_commonModes, modeName);
if (NULL != set) {
CFTypeRef context[2] = {rl, modeName};
//將common mode items中的每一個(gè)item添加到傳入的mode中
CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
}
}
}
當(dāng)調(diào)用CFRunLoopAddCommonMode函數(shù)時(shí),會(huì)將新的mode加入到該runloop的common modes集合中,同時(shí),會(huì)將當(dāng)前common mode items中的所有元素,添加到新的mode中。
如此一來,無論run loop被切換到哪一個(gè)mode,只要這個(gè)mode被加入到kCFRunLoopCommonModes中,就可以響應(yīng)那些被添加到kCFRunLoopCommonModes的sources/timers/observers。
2. CFRunLoopAddSource/CFRunLoopAddObserver/CFRunLoopAddTimer
可以在將source/timer/observer三種對(duì)象加入到run loop時(shí)傳入kCFRunLoopCommonModes參數(shù),這樣run loop運(yùn)行在common modes的mode時(shí),這些source/timer/observer就會(huì)生效。這里僅放一個(gè)CFRunLoopAddTimer中與common modes有關(guān)的代碼,其他兩種(source/observer)代碼都是類似的。
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
//判斷是否是將timer加入到kCFRunLoopCommonModes
if (modeName == kCFRunLoopCommonModes) {
//獲取common modes集合
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
//如果common modes items為空,則創(chuàng)建一個(gè)common modes items的集合
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
//將傳入的timer source加入到common mode items中
CFSetAddValue(rl->_commonModeItems, rlt);
//將timer添加到common modes中的每一個(gè)mode中
if (NULL != set) {
CFTypeRef context[2] = {rl, rlt};
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
}
} else {
//非common mode的操作
}
}
將一個(gè)timer加入到kCFRunLoopCommonModes中后,會(huì)將這個(gè)timer加入到common mode items里,同時(shí)也會(huì)將這個(gè)timer加入到common modes集合中的每一個(gè)mode中,也就是說,該函數(shù)會(huì)幫你更新所有在common modes集合中的mode,這樣就達(dá)到了無論運(yùn)行在任意一個(gè)common mode時(shí),都可以使這個(gè)timer生效。
CFRunLoopSource/CFRunLoopTimer/CFRunLoopObserver
我們可以通過將source/timer/observer這三種對(duì)象加入到run loop中,當(dāng)有事件發(fā)生時(shí),通過回調(diào)接受通知。在加入到run loop時(shí),必須指定一個(gè)mode。當(dāng)然也可以從一個(gè)run loop中移除上述三種對(duì)象。
CFRunLoopSourceRef
Input source是事件發(fā)生的來源,通常產(chǎn)生異步事件,比如消息到達(dá)網(wǎng)絡(luò)端口或者用戶執(zhí)行的操作。它的定義如下:
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;//toll-free bridge
struct __CFRunLoopSource {
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0;//source0
CFRunLoopSourceContext1 version1;//source1
} _context;
};
在CFRunLoopSourceRef中,最主要的是兩個(gè)變量CFRunLoopSourceContext和CFRunLoopSourceContext1,它們用union來聲明,意味著有兩種不同的source,source0和source1。
source0
source0是一般響應(yīng)應(yīng)用程序事件,例如按鈕的響應(yīng)事件。當(dāng)需要發(fā)送source0事件時(shí),調(diào)用CFRunLoopSourceSignal函數(shù)將這個(gè)source標(biāo)記為待觸發(fā),但是該函數(shù)不能喚醒runloop,需要再調(diào)用CFRunLoopWakeUp方法喚醒對(duì)應(yīng)的run loop,這個(gè)source0事件才會(huì)被觸發(fā)。Core Foundation中的CFSocket使用的是source0的方式實(shí)現(xiàn)。定義如下:
typedef struct {
CFIndex version;//0代表是source0
void (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
它包含三個(gè)回調(diào),
-
schedule:當(dāng)添加source0到run loop時(shí),會(huì)調(diào)用一次schedule回調(diào)方法; -
perform:當(dāng)觸發(fā)source0時(shí),會(huì)調(diào)用perform回調(diào)方法; -
cancel:當(dāng)移除或者run loop銷毀時(shí),會(huì)調(diào)用cancel回調(diào)方法。
source1
source1由run loop和內(nèi)核管理,使用mach port進(jìn)行通信,用于通過內(nèi)核進(jìn)行進(jìn)程間通信,也可以用于線程間的通信。source1能夠?qū)un loop喚醒。Core Foundation中的CFMachPort和CFMessagePort通過source1的方式實(shí)現(xiàn)。定義如下:
typedef struct {
CFIndex version;//1代表source1
mach_port_t (*getPort)(void *info);
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#endif
} CFRunLoopSourceContext1;
其中
-
getPort函數(shù)指針提供一個(gè)獲取port的函數(shù) -
perform函數(shù)是回調(diào)函數(shù)
有趣的是,
CFMessagePort通常用于進(jìn)程間通信,如iOS越獄開發(fā),常用的場(chǎng)景是前端有一個(gè)UI程序用于界面展示,后端有一個(gè)daemo精靈程序用于任務(wù)處理。而官方文檔中有提到,CFMessagePort已不能再iOS7之后的系統(tǒng)中使用。來源。
CFRunLoopSourceRef的使用
Core Foundation提供了提供了與input sourc交互的函數(shù)
-
CFRunLoopSourceGetContext:創(chuàng)建source0或者source1變量 -
CFRunLoopSourceCreate:創(chuàng)建CFRunLoopSourceRef -
CFRunLoopAddSource:添加CFRunLoopSourceRef到run loop中
CFRunLoopTimerRef
CFRunLoopTimerRef是和NSTimer toll-free bridge的計(jì)時(shí)器。Run loop timer并不一定可靠,如果加入的mode沒有運(yùn)行或者當(dāng)前run loop在執(zhí)行一段耗時(shí)的操作,run loop timer可能不會(huì)被觸發(fā)。而且Run loop timer的觸發(fā)時(shí)間依賴于計(jì)劃的時(shí)間間隔,而不是實(shí)際運(yùn)行的時(shí)間間隔,比如一個(gè)5秒重復(fù)的計(jì)時(shí)器,在第二次時(shí)延時(shí)了2秒,那么第三次的執(zhí)行時(shí)間不會(huì)改變,仍然是第15秒時(shí)執(zhí)行。
Run loop timer可以同時(shí)加入到多個(gè)run loop mode中,但中只會(huì)在第一個(gè)加入的run loop內(nèi)生效。
CFRunLoopTimerRef主要包含了interval參數(shù)和Callback回調(diào)方法。
Run loop timer關(guān)于時(shí)間計(jì)算的機(jī)制以后再深入了解
CFRunLoopObserverRef
CFRunLoopObserverRef是一個(gè)觀察者,它包含了一個(gè)回調(diào)指針和一個(gè)Activity參數(shù),用于表明接受的run loop事件,具體事件定義如下,當(dāng)run loop處于不同狀態(tài)時(shí),會(huì)通知obsever。
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
};
blocks
在__CFRunLoop的定義中,還有兩個(gè)關(guān)于block鏈表的定義
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
Run loop還提供了一個(gè)CFRunLoopPerformBlock函數(shù)用于添加block,和source0類似,這個(gè)方法不會(huì)主動(dòng)喚醒run loop,需要調(diào)用CFRunLoopWakeUp函數(shù)主動(dòng)喚醒run loop。
CFRunLoopPerformBlock的定義如下
void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void))
在run loop運(yùn)行的每一次循環(huán)中,會(huì)多次檢查當(dāng)前是否有需要執(zhí)行的block,如果有則會(huì)執(zhí)行傳入的block。
CFRunLoopGet
在通常開發(fā)時(shí),我們通常不需要關(guān)心run loop的生命周期。系統(tǒng)會(huì)自動(dòng)在主線程幫我們創(chuàng)建run loop對(duì)象。可以通過如下接口獲得主線程的run loop對(duì)象,該方法返回一個(gè)CFRunLoopRef實(shí)例。
CFRunLoopRef CFRunLoopGetMain(void);
當(dāng)我們創(chuàng)建一個(gè)新的線程時(shí),默認(rèn)是沒有初始化run loop對(duì)象的,需要調(diào)用函數(shù)獲取當(dāng)前線程的run loop對(duì)象。
CFRunLoopRef CFRunLoopGetCurrent(void);
如此看來,創(chuàng)建run loop對(duì)象的函數(shù)就是Core Foundation內(nèi)部實(shí)現(xiàn)的了,CFRunLoopGetMain和CFRunLoopGetCurrent這兩個(gè)函數(shù)內(nèi)部都會(huì)調(diào)用同一個(gè)方法_CFRunLoopGet0(pthread_t t),來看下具體的代碼。
這里簡(jiǎn)單提一下TSD(thread specific data)的概念,在多線程環(huán)境中,因?yàn)閿?shù)據(jù)空間是共享的,所以全局變量也為所有線程所共有。所以當(dāng)需要一個(gè)僅在當(dāng)前線程中可以訪問的數(shù)據(jù)時(shí),使用TSD來存儲(chǔ)。TSD存儲(chǔ)的數(shù)據(jù)僅在當(dāng)前線程有效,但是可以跨函數(shù)訪問。
run loop對(duì)象就存儲(chǔ)在TSD中。
CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
//__CFRunLoops是全局保存runloop對(duì)象的dict,首次運(yùn)行時(shí)初始化該dict
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
//創(chuàng)建全局ditc,并添加主線程的runloop 對(duì)象
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//通過__CFRunLoopCreate方法創(chuàng)建主線程的run loop對(duì)象
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
//將dict與__CFRunLoops指針互換,然后釋放ditc
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
}
//通過線程對(duì)象獲取對(duì)應(yīng)的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
//如果該線程還沒有創(chuàng)建runloop對(duì)象,那么初始化該線程的runloop對(duì)象
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
}
//判斷是否獲取當(dāng)前線程的runloop對(duì)象
if (pthread_equal(t, pthread_self())) {
//將run loop對(duì)象存放到TSD中
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
//在這里設(shè)置銷毀的回調(diào)方法,當(dāng)線程生命周期結(jié)束時(shí)銷毀runloop對(duì)象
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
該函數(shù)的流程如下
- 第一次進(jìn)入時(shí),會(huì)創(chuàng)建一個(gè)全局的dict,用于存儲(chǔ)每一個(gè)線程對(duì)應(yīng)的run loop對(duì)象,同時(shí)也會(huì)初始化主線程的run loop對(duì)象。
- 根據(jù)傳入的線程獲取對(duì)應(yīng)的run loop對(duì)象,若為空,則創(chuàng)建一個(gè)run loop對(duì)象,并添加到全局dict中。同時(shí)也會(huì)將這個(gè)run loop放到對(duì)應(yīng)線程的TSD中并設(shè)置一個(gè)線程結(jié)束時(shí)的銷毀函數(shù)回調(diào)。
通過上面的函數(shù),我們可以獲取到當(dāng)前線程的run loop對(duì)象。在執(zhí)行前面所講的添加source/timer/observers函數(shù)時(shí),都需要傳入run loop對(duì)象。
系統(tǒng)會(huì)啟動(dòng)主線程的run loop的運(yùn)行,對(duì)于其他線程,需要我們?cè)讷@取run loop對(duì)象后主動(dòng)啟動(dòng)。
CFRunLoopRun
首先通過一張圖,了解下CFRunLoopRun的主要邏輯。

PS:圖中左邊的source0(port)應(yīng)該是source1(port)
在Core Foundation提供了兩個(gè)函數(shù)供我們啟動(dòng)run loop,CFRunLoopRun和CFRunLoopRunInMode,函數(shù)聲明如下:
//無參數(shù),直接啟動(dòng)run loop,運(yùn)行在default mode
void CFRunLoopRun(void);
//設(shè)置run loop運(yùn)行的mode,有效期以及是否在運(yùn)行source 0事件后直接退出,返回值為run loop退出的原因字段
SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
這兩個(gè)函數(shù)都會(huì)調(diào)用CFRunLoopRunSpecific函數(shù),該函數(shù)實(shí)現(xiàn)如下,注釋中的數(shù)字對(duì)應(yīng)圖中的順序
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
//判斷mode中是否有source,timer或者block事件,如果沒有,run loop會(huì)立即退出
if (__CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
return kCFRunLoopRunFinished;
}
int32_t retVal = kCFRunLoopRunFinished;
//1. run loop即將進(jìn)入,通知Observers
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
do {
//2. 通知觀察者,即將觸發(fā)計(jì)時(shí)器
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//3. 通知觀察者即將觸發(fā)source0(非port based)輸入源
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//處理blocks
__CFRunLoopDoBlocks(rl, rlm);
//4. 處理source0,也就是非port based輸入源
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//處理完source0后會(huì)再處理下blocks
__CFRunLoopDoBlocks(rl, rlm);
}
//5. 檢查是否有需要處理的source1事件,通常是系統(tǒng)級(jí)事件,倒數(shù)第三個(gè)參數(shù)傳0表示立即返回
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
//跳轉(zhuǎn)到handle_msg直接去處理source1事件
goto handle_msg;
}
//沒有source1則直接進(jìn)入睡眠
//6. 通知觀察者Runloop即將進(jìn)入睡眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//7. 調(diào)用系統(tǒng)內(nèi)核方法mach_msg,切換到內(nèi)核態(tài)接受消息
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
//當(dāng)有source1,timer或者手動(dòng)喚醒時(shí),會(huì)退出睡眠態(tài)
//8. 通知觀察者Runloop退出等待
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
//9. 退出睡眠后,需要處理些事件
//計(jì)時(shí)器需要觸發(fā)
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
//觸發(fā)計(jì)時(shí)器
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
//計(jì)算下一次觸發(fā)的時(shí)間
__CFArmNextTimerInMode(rlm, rl);
}
} else if (livePort == dispatchPort) {
//如果有dispatch到main_queue的block,執(zhí)行block。
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {//source1事件
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
}
//再次執(zhí)行一次block回調(diào)
__CFRunLoopDoBlocks(rl, rlm);
//判斷是否需要退出run loop
if (sourceHandledThisLoop && stopAfterHandle) {
//啟動(dòng)run loop時(shí)設(shè)置了執(zhí)行source0后立即退出
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
//啟動(dòng)run loop時(shí)設(shè)置了超時(shí)時(shí)間
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
//run loop被設(shè)置為停止
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
//run loop mode被設(shè)置為停止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
//當(dāng)前mode被移除了
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
//10. run loop已經(jīng)退出,通知observers
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return retVal;
}
通過代碼可以了解到
- run loop通過do…while循環(huán)實(shí)現(xiàn),只要不滿足退出的條件,run loop就會(huì)睡眠或者運(yùn)行。
- run loop需要添加source,timer或者block事件才能運(yùn)行,否則會(huì)直接退出
- run loop進(jìn)入休眠時(shí)調(diào)用
mach_msg函數(shù)切換到內(nèi)核態(tài)(當(dāng)我們?cè)赼pp運(yùn)行時(shí)點(diǎn)擊暫停,就可以看到調(diào)用棧停留在mach_msg_trap()這個(gè)方法)

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è)source0 加到對(duì)應(yīng)的線程去,同樣的,如果對(duì)應(yīng)線程沒有 RunLoop 該方法也會(huì)失效。
蘋果對(duì)RunLoop的應(yīng)用
了解了RunLoop的實(shí)現(xiàn),我們看一看蘋果如何使用RunLoop的。該節(jié)主要參考深入理解RunLoop,這里僅做部分展開。
在viewDidLoad方法中增加斷點(diǎn),打印[NSRunLoop currentRunLoop]可以打印出當(dāng)前線程的runLoop對(duì)象的一些詳細(xì)信息
<CFRunLoop
current mode = kCFRunLoopDefaultMode,
common modes = {
"UITrackingRunLoopMode"
"kCFRunLoopDefaultMode"
}
common mode items = {
//source 0
CFRunLoopSource {
order = -1, version = 0, callout = PurpleEventSignalCallback
}
CFRunLoopSource {
order = -1, version = 0, callout = __handleEventQueue
}
CFRunLoopSource {
order = 0, version = 0, callout = FBSSerialQueueRunLoopSourceHandler
}
CFRunLoopSource {
order = -2, version = 0, callout = __handleHIDEventFetcherDrain
}
//source 1
CFRunLoopSource {
order = 0, port = 43275
}
CFRunLoopSource {
order = -1, version = 1, callout = PurpleEventCallback
}
CFRunLoopSource {
order = 0, port = 42755
}
//UI繪制相關(guān)
CFRunLoopObserver {
activities = 0xa0, order = 1999000, callout = _beforeCACommitHandler
}
CFRunLoopObserver {
activities = 0xa0, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv
}
CFRunLoopObserver {
activities = 0xa0, order = 2001000, callout = _afterCACommitHandler
}
//自動(dòng)釋放池(auto release pool)相關(guān)
CFRunLoopObserver {
activities = 0x1, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
}
CFRunLoopObserver {
activities = 0xa0, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
}
CFRunLoopObserver {
activities = 0x20, order = 0, callout =_UIGestureRecognizerUpdateObserver
}
}
modes = {
//RunLoop包含的mode以及mode中的observers/sources/timers
}
AutoreleasePool
蘋果在主線程RunLoop的CommonModes中注冊(cè)了兩個(gè)Observer,其中回調(diào)函數(shù)都是_wrapRunLoopWithAutoreleasePoolHandler。
<CFRunLoopObserver>{
order = -2147483647, activities = 0x1, callout = _wrapRunLoopWithAutoreleasePoolHandler
}
<CFRunLoopObserver>{
order = 2147483647, activities = 0xa0, callout = _wrapRunLoopWithAutoreleasePoolHandler
}
第一個(gè)Observer設(shè)置了最高優(yōu)先級(jí)-2147483647,在進(jìn)入RunLoop時(shí)會(huì)觸發(fā)(0x1)。
第二個(gè)Observer設(shè)置了最低優(yōu)先級(jí)2147483647,在RunLoop進(jìn)入睡眠或者退出時(shí)觸發(fā)(0xa0)。
_wrapRunLoopWithAutoreleasePoolHandler
通過在XCode中添加Symbolic Breakpoint增加斷點(diǎn),查看_wrapRunLoopWithAutoreleasePoolHandler對(duì)應(yīng)匯編的代碼。

啟動(dòng)App后,會(huì)進(jìn)入該斷點(diǎn),可以看到代碼中有兩處地方會(huì)跳轉(zhuǎn)到其他函數(shù),分別是NSPushAutoreleasePool和NSPopAutoreleasePool,根據(jù)名字可以看出,一個(gè)是autlreleasePool的創(chuàng)建,另一個(gè)是autlreleasePool的釋放,推測(cè)在該函數(shù)中會(huì)判斷RunLoop當(dāng)前的狀態(tài),然后執(zhí)行不同的函數(shù)。
然后我們繼續(xù)對(duì)NSPushAutoreleasePool和NSPopAutoreleasePool添加斷點(diǎn),最終調(diào)用函數(shù)分別是objc_autoreleasePoolPush和objc_autoreleasePoolPop。
第一個(gè)Observer在進(jìn)入RunLoop時(shí),創(chuàng)建autoreleasePool,其order=-2147483647保證了是所有回調(diào)之前調(diào)用。
第二個(gè)Observer在RunLoop休眠時(shí),首先釋放autoreleasePool,然后創(chuàng)建autoreleasePool;在RunLoop退出時(shí),釋放autoreleasePool,其order=2147483647保證了是所有回調(diào)之后調(diào)用。
總結(jié)
本文主要對(duì)Core Foundation框架中的RunLoop源碼進(jìn)行學(xué)習(xí),了解了RunLoop的實(shí)現(xiàn)原理,后續(xù)會(huì)對(duì)RunLoop的應(yīng)用進(jìn)一步探索研究。