轉(zhuǎn)自 iOS RunLoop 詳解
image.png

Runloop 是和線程緊密相關(guān)的基礎(chǔ)組件,是很多多線程有關(guān)功能的幕后功臣。盡管在平常使用中幾乎不會太直接用到,理解 Runloop 有利于我們更加深入的理解 iOS 的多線程模型。
本文從如下幾個方面理解RunLoop的相關(guān)知識點(diǎn)。
- RunLoop 概念
- RunLoop 實(shí)現(xiàn)
- RunLoop 運(yùn)用
- RunLoop 應(yīng)用
RunLoop 概念
RunLoop 介紹
RunLoop 是什么?RunLoop 還是比較顧名思義的一個東西,說白了就是一種循環(huán),只不過它這種循環(huán)比較高級。一般的 while 循環(huán)會導(dǎo)致 CPU 進(jìn)入忙狀態(tài),而 RunLoop 則是一種“閑”等待,這部分可以類比 Linux 下的 epoll。當(dāng)沒有事件時,RunLoop 會進(jìn)入休眠狀態(tài),有事件發(fā)生時,RunLoop 會去找相應(yīng)的 Hander 處理事件。RunLoop 可以讓線程需要做事的時候忙起來,不需要的話就讓線程休眠。
從代碼上看,RunLoop 其實(shí)就是一個對象,它的結(jié)構(gòu)如下,源碼看這里:
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp 內(nèi)核向該端口發(fā)送消息可以喚醒 RunLoop
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; // RunLoop 對應(yīng)的線程
uint32_t _winthread;
CFMutableSetRef _commonModes; // 存儲的是字符串,記錄所有標(biāo)記為 common 的 mode
CGMutableSetRef _commonModeItems; // 存儲所有 commonMode 的 item(source、timer、observer)
CFRunLoopModeRef _currentMode; // 當(dāng)前運(yùn)行的 mode
CFMutableSetRef _modes; // 存儲的是 CFRunLoopModeRef
struct _block_item *_blocks_head; // doblocks 的時候用到
struct _block_item *_blocks_tail;
CGTypeRef _counterpart;
}
可見,一個 RunLoop 對象,主要包含了一個線程,若干個 Mode,若干個 commonMode,還有一個當(dāng)前運(yùn)行的 Mode。
RunLoop 與線程
當(dāng)我們需要一個常駐線程,可以讓線程需要做事的時候忙起來,不需要的話就讓線程休眠。我們就在線程里面執(zhí)行下面這個代碼,一直等待消息,線程就不會退出了。
do {
// 獲取消息
// 處理消息
} while (消息 != 退出)
上面的這種循環(huán)模型被稱作 Event Loop,事件循環(huán)模型在眾多系統(tǒng)里都有實(shí)現(xiàn),RunLoop 實(shí)際上就是一個對象,這個對象管理了其需要處理的事件和消息,并提供了一個入口函數(shù)來執(zhí)行上面 Event Loop 的邏輯。線程執(zhí)行了這個函數(shù)后,就會一直處于函數(shù)內(nèi)部“接收消息->等待->處理”的循環(huán)中,直到這個循環(huán)結(jié)束(比如傳入 quit 的消息),函數(shù)返回。
下圖描述了Runloop運(yùn)行流程(基本描述了上面Runloop的核心流程,當(dāng)然可以查看官方The Run Loop Sequence of Events描述):
runLoopFCD.jpg

整個流程并不復(fù)雜(需要注意的就是黃色區(qū)域的消息處理中并不包含source0,因?yàn)樗谘h(huán)開始之初就會處理),整個流程其實(shí)就是一種Event Loop的實(shí)現(xiàn),其他平臺均有類似的實(shí)現(xiàn),只是這里叫做RunLoop。
RunLoop與線程的關(guān)系如下圖
image.png

圖中展現(xiàn)了 RunLoop 在線程中的作用:從 input source 和 timer source 接受事件,然后在線程中處理事件。
RunLoop 和線程是綁定在一起的。每個線程(包括主線程)都有一個對應(yīng)的 RunLoop 對象。我們并不能自己創(chuàng)建 RunLoop 對象,但是可以獲取到系統(tǒng)提供的 RunLoop 對象。
主線程的 RunLoop 會在應(yīng)用啟動的時候完成啟動,其他線程的 RunLoop 默認(rèn)并不會啟動,需要我們手動啟動。
RunLoop Mode
Mode 可以視為事件的管家,一個 Mode 管理著各種事件,它的結(jié)構(gòu)如下:
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; // mode 名稱
Boolean _stopped; // mode 是否被終止
char _padding[3];
// 幾種事件
CFMutableSetRef _sources0; // source0
CFMutableSetRef _sources1: // sources1
CFMutableArrayRef _observers; // 通知
CFMutableArrayRef _times; // 定時器
CFMutableDictionaryRef _portToV1SourceMap; // 字典 key 是 mach_port_t,value 是 CFRunLoopSourceRef
__CFPortSet _portSet; // 保存所有需要監(jiān)聽的 port,比如 _weakUpPort, _timerPort 都保存在這個數(shù)據(jù)中
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if DEPLOYMENAT_TARGET_WINDOWS
DWORD _msgQMask;
void (* _msgPump)(void);
#endif
uint64_t _timerSoftDeadline;
uint64_t _timerHardDeadline;
}
一個 CFRunLoopMode 對象有一個 name,若干 source0、 source1、timer、observer 和若干 port,可見事件都是由 Mode 在管理, 而 RunLoop 管理 Mode。
從源碼很容易看出,RunLoop 總是運(yùn)行在某種特定的 CFRunLoopModeRef 下(每次運(yùn)行 __CFRunLoopRun() 函數(shù)時必須指定 Mode)。而通過 CFRunLoopRef 對應(yīng)結(jié)構(gòu)體的定義可以很容易知道每種 RunLoop 都可以包含個 Mode,每個 Mode 有包含 Source/Timer/Observer。每次調(diào)用 RunLoop 的主函數(shù) __CFRunLoopRun() 時必須指定一種 Mode,這個 Mode 稱為 _curentMode,當(dāng)切換 Mode 時必須退出當(dāng)前 Mode,然后重新進(jìn)入 RunLoop 以保證不同 Mode 的 Source/Timer/Observer 互不影響。
image.png

如圖所示,RunLoop Mode 實(shí)際上是 Source,Timer 和 Observer 的集合,不同的 Mode 把不同組的 Source,Timer 和 Observer 隔絕開來。RunLoop 在某個時刻只能跑在一個 Mode 下,處理這一個 Mode 當(dāng)中的 Source,Timer 和 Observer。
蘋果文檔中提到的 Mode 有五個,分別是:
- NSDefaultRunLoopMode
- NSConnectionReplyMode
- NSModalPanelRunLoopMode
- NSEventTrackingRunLoopMode
- NSRunLoopCommonModes
iOS 中公開暴露出來的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes。NSRunLoopCommonModes 實(shí)際上是一個 Mode 的集合,默認(rèn)包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode(注意:并不是說 RunLoop 會運(yùn)行在 kCFRunLoopCommonModes 這種模式下,而是相當(dāng)于分別注冊了 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode。當(dāng)然你也可以通過調(diào)用 CFRunLoopAddCommonMode() 方法將自定義 Mode 放到 kCFRunLoopCommonModes 組合)。
五種 Mode 的介紹如下:
| mode | name | description |
|---|---|---|
| Default | NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation) | 用的最多的模式,大多數(shù)情況下應(yīng)該使用該模式開始 RunLoop 并配置 input source |
| Connection | NSConnectionReplyMode (Cocoa) | Cocoa 用這個模式結(jié)合 NSConnection 對象監(jiān)測回應(yīng),我們應(yīng)該很少使用這種模式 |
| Modal | NSModalPanelRunLoopMode (Cocoa) | Cocoa 用此模式來表示用于模態(tài)面板的事件 |
| Event tracking | NSEventTrackingRunLoopMode (Cocoa) | Cocoa 使用此模式在鼠標(biāo)拖動 loop 和其它用戶界面跟蹤 loop 期間限制傳入事件 |
| Common modes | NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation) | 這是一種可配置的常用模式。將輸入源與這些模式相關(guān)聯(lián)會與組中的每個模式相關(guān)聯(lián)。Cocoa applications 里面包括 Default、Modal 和 Event tracking。Core Foundation 只包含默認(rèn)模式,你可以自己把自定義 mode 用 CFRunLoopAddCommonMode 函數(shù)加入到集合中。 |
RunLoop Source
RunLoop source 分為 Source、Observer、Timer 三種,它們統(tǒng)稱為 ModelItem。
CFRunLoopSource
根據(jù)官方的描述,CFRunLoopSource 是對 input sources 的抽象。CFRunLoopSource 分為 source0 和 source1 兩個版本,它的結(jié)構(gòu)如下:
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits; // 用于標(biāo)記 Signaled 狀態(tài),source0 只有在被標(biāo)記為 Signaled 狀態(tài),才會被處理
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
source0 是 App 內(nèi)部事件,由 App 自己管理的 UIEvent、CFSocket 都是 source0.當(dāng)一個 source0 事件準(zhǔn)備執(zhí)行的時候,必須要先把它標(biāo)記為 signal 狀態(tài),以下是 source0 的結(jié)構(gòu)體:
typedef struct {
CFIndex version;
void *info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
source0 是非基于 Port 的。只包含了一個回調(diào)(函數(shù)指針),它并不能主動觸發(fā)事件。石永師,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標(biāo)記為待處理,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。
source1 由 RunLoop 和內(nèi)核管理,source1 帶有 mach_port_t,可以接收內(nèi)核消息并觸發(fā)回調(diào),以下是 source1 的結(jié)構(gòu)體:
typedef struct {
CFIndex version
void *info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
#if ((TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
mach_port_t (*getPort)(void *info);
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
void * (*getPort)(void *info);
void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
Source1 除了包含回調(diào)指針外包含一個 mach port,Source1 可以監(jiān)聽系統(tǒng)端口和通過內(nèi)核和其他線程通信,接收、分發(fā)系統(tǒng)事件,它能夠主動喚醒 RunLoop(由操作系統(tǒng)內(nèi)核進(jìn)行管理,例如 CFMessagePort 消息)。官方也指出可以自定義 Source,因此對于 CFRunLoopSourceRef 來說它更像一種協(xié)議,框架已經(jīng)默認(rèn)定義了兩種實(shí)現(xiàn),如果有必要開發(fā)人員也可以自定義,詳細(xì)情況可以查看官方文檔。
CFRunLoopObserver
CFRunLoopObserver 是觀察者,可以觀察 RunLoop 的各種狀態(tài),并拋出回調(diào)
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopARef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
CFRunLoopObserver 可以觀察的狀態(tài)有如下6種:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入 run loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 被喚醒但是還沒開始處理事件
kCFRunLoopExit = (1UL << 7), // run loop 已經(jīng)退出
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
RunLoop 通過監(jiān)控 Source 來決定有沒有任務(wù)要做,除此之外,我們還可以用 RunLoop Observer 來監(jiān)控 RunLoop 本身的狀態(tài)。RunLoop Observer 可以監(jiān)控上面的 RunLoop 事件,具體流程如下圖。
runLoopFCD2.jpg

CFRunLoopTimer
CFRunLoopTimer 是定時器,可以在設(shè)定的時間點(diǎn)拋出回調(diào),它的結(jié)構(gòu)如下:
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits; // 標(biāo)記fire狀態(tài)
pthread_mutex_t _block;
CFRunLoopRef _runLoop; // 添加該 timer 的 runloop
CFMutableSetRef _rlModes; // 存放所有包含該 timer 的 modeName,意味著一個 timer 可能會在多個 mode 中存在
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; // 理想時間間隔 /* immutable */
CFTimeInterval _tolerance; // 時間偏差 /* mutable */
uint64_t _fireTSR; /* TRS units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
另外根據(jù)官方文檔的描述,CFRunLoopTimer和NSTimer是toll-free bridged的,可以相互轉(zhuǎn)換。
CFRunLoopTimer is “toll-free bridged” with its Cocoa Foundation counterpart, NSTimer. This means that the Core Foundation type is interchangeable in function or method calls with the bridged Foundation object.
所有 CFRunLoopTimer 具有以下特征:
- CFRunLoopTimer 是定時器,可以在設(shè)定的時間點(diǎn)拋出回調(diào)
- CFRunLoopTimer 和 NSTimer 是 toll-free bridged 的,可以互相轉(zhuǎn)換
RunLoop 實(shí)現(xiàn)
下面從以下3個方面介紹RunLoop的實(shí)現(xiàn)。
- 獲取 RunLoop
- 添加 Mode
- 添加 RunLoop Source
獲取 RunLoop
從蘋果開放的 API 來看,不允許我們直接創(chuàng)建 RunLoop 對象,只能通過以下幾個函數(shù)來獲取 RunLoop:
- CFRunLoopRef CFRunLoopGetCurrent(void)
- CFRunLoopRef CFRunLoopGetMain(void)
- +(NSRunLoop *)currentRunLoop
- +(NSRunLoop *)mainRunLoop
前兩者是 Core Foundation 中的 API,后兩者是 Foundation 中的 API。
那么RunLoop是什么時候被創(chuàng)建的呢?
我們從下面幾個函數(shù)內(nèi)部看看。
CFRunLoopGetCurrent
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
// 傳入當(dāng)前線程
return _CFRunLoopGet0(pthread_self());
}
在 CFRunLoopGetCurrent 函數(shù)中調(diào)用了 _CFRunLoopGet0(),傳入的參數(shù)是當(dāng)前線程 pthread_self()。這里可以看出,CFRunLoopGetCurrent 函數(shù)必須要在線程內(nèi)部調(diào)用,獲取當(dāng)前線程的 RunLoop。也就是說子線程的 RunLoop 必須要在子線程內(nèi)部獲取。
CFRunLoopGetMain
// 取主線程的 RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; no retain needed
// 傳入主線程
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
在 CFRunLoopGetMain 函數(shù)內(nèi)部也調(diào)用了 _CFRunLoopGet0(),傳入的參數(shù)是主線程 pthread_main_thread_np()??梢钥闯觯珻FRunLoopGetMain() 不管在主線程還是子線程中調(diào)用,都可以獲取到主線程的 RunLoop。
CFRunLoopGet0
前面兩個函數(shù)都使用了 CFRunLoopGet0 實(shí)現(xiàn)傳入線程的函數(shù),下面看下 CFRunLoopGet0 的結(jié)構(gòu)是咋樣的。
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFSpinLock_t loopsLock = CFSpinLockInit;
// t==0 is a synonym for "main thread" that always works
// 根據(jù)線程取 RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
// 如果存儲 RunLoop 的字典不存在
if (!__CFRunLoops) {
__CFSpinUnlock(&loopsLock);
// 創(chuàng)建一個臨時字典 dict
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 創(chuàng)建主線程的 RunLoop
CFRunLoopRef mainLoop = _CFRunLoopCreate(pthread_main_thread_np());
// 把主線程的 RunLoop 保存到 dict 中,key 是線程,value 是 RunLoop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
// 此處 NULL 和 __CFRunLoops 指針都指向 NULL,匹配,所有將 dict 寫到 __CFRunLoops
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void *volatile *)&_CFRunLoops)) {
// 釋放 dict
CFRelease(dict);
}
// 釋放 mainrunloop
CFRelease(mainLoop);
__CFSpinLock(&loopsLock);
}
//以上說明,第一次進(jìn)來的時候,不管是getMainRunloop還是get子線程的runloop,主線程的runloop總是會被創(chuàng)建
//從字典__CFRunLoops中獲取傳入線程t的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
// 如果沒有獲取到
if (!loop) {
// 根據(jù)線程 t 創(chuàng)建一個 runloop
CFRunLoopRef newLoop = __CFRunLoopCreat(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
// 把 newLoop 存入字典 __CFRunLoops,key 是線程 t
CFDictionarySetValue(__CFRunLoops, pthreadPoint(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDellocate may end up taking it
__CFSpinUnlock(&loopsLock);
CFRelease(newLoop);
}
// 如果傳入線程就是當(dāng)前線程
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
// 注冊一個回調(diào),當(dāng)線程銷毀時,銷毀對應(yīng)的 RunLoop
_CFSetTSD(__CFTSDkeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
這段代碼可以總結(jié)得出以下結(jié)論:
- RunLoop 和線程是一一對應(yīng)的,對應(yīng)的方式是以 key-value 的方式保存在一個全局字典中
- 主線程的 RunLoop 會在初始化全局字典時創(chuàng)建
- 子線程的 RunLoop 會在第一次獲取的時候創(chuàng)建,如果不獲取的話就一直不會被創(chuàng)建
- RunLoop 會在線程銷毀時銷毀
添加 Mode
在 Core Foundation 中,針對 Mode 的操作,蘋果只開放了以下三個 API(Cocoa 中也有功能一樣的函數(shù),不再列出):
- CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode)
- CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl)
- CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl)
CFRunLoopAddCommonMode Adds a mode to the set of run loop common modes. 向當(dāng)前RunLoop的common modes中添加一個mode。
CFRunLoopCopyCurrentMode Returns the name of the mode in which a given run loop is currently running. 返回當(dāng)前運(yùn)行的mode的name
CFRunLoopCopyAllModes Returns an array that contains all the defined modes for a CFRunLoop object. 返回當(dāng)前RunLoop的所有mode
我們沒有辦法直接創(chuàng)建一個 CFRunLoopMode 對象,但是我們可以調(diào)用 CFRunLoopAddCommonMode 傳入一個字符串向 RunLoop 中添加 Mode,傳入的字符串即為 Mode 的名字,Mode 對象應(yīng)該是此時在 RunLoop 內(nèi)部創(chuàng)建的。下面來看一下源碼。
CFRunLoopAddCommonMode
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
__CFRunLoopLock(rl);
// 看 rl 中是否已經(jīng)有這個 mode,如果有就什么也不做
if (!CFSetContainsValue(rl->_commonModes, modeName)) {
CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
// 把 modeName 添加到 RunLoop 的 _commonModes 中
CFSetAddValue(rl->_commonModes, modeName);
if (NULL != set) {
CFTypeRef context[2] = {rl, modeName};
/* add all common-modes items to new mode */
// 這里調(diào)用 CFRunLoopAddSource/CFRunLoopAddObserver/CFRunLoopAddTimer 的時候會調(diào)用
// __CFRunLoopFindMode(rl, modeName, true), CFRunLoopMode 對象在這個時候被創(chuàng)建
CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
CFRelease(set);
}
} else {
}
__CFRunLoopUnlock(rl);
}
可以看得出:
- modeName 不能重復(fù),modeName 是 mode 的唯一標(biāo)識符
- RunLoop 的 _commonModes 數(shù)組存放所有被標(biāo)記為 common 的 mode 的名稱
- 添加 commonMode 會把commonModelItems 數(shù)組中的所有 source 同步到新添加的 mode 中
- CFRunLoopMode 對象在 CFRunLoopAddItemsToCommonMode 函數(shù)中調(diào)用 CFRunLoopFindMode 時被創(chuàng)建
CFRunLoopCopyCurrentMode/CFRunLoopCopyAllModes
CFRunLoopCopyCurrentMode 和 CFRunLoopCopyAllModes 的內(nèi)部邏輯比較簡單,直接取 RunLoop 的 _currentMode 和 _modes 返回,就不貼源碼了。
添加 RunLoop Source (ModeItem)
我們可以通過以下接口添加/移除各種事件:
- void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
- void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
- void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode)
- void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode)
- void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
- void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
CFRunLoopAddSource
CFRunLoopAddSource 的代碼結(jié)構(gòu)如下:
//添加source事件
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
if (!__CFIsValid(rls)) return;
Boolean doVer0Callout = false;
__CFRunLoopLock(rl);
// 如果是 kCFRunLoopCommonModes
if (modeName == kCFRunLoopCommonModes) {
// 如果 runloop 的 _commonModes 存在,則 copy 一個新的復(fù)制給 set
CFSetRef set = rl->commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
// 如果 runloop 的 _commonModeItems 為空
if (NULL == rl->_commonModeItems) {
// 先初始化
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
// 把傳入的 CFRunLoopSourceRef 加入 _commonModeItems
CFSetAddValue(rl->_commonModeItems, rls);
// 如果剛才 set copy 到的數(shù)組里面有數(shù)據(jù)
if (NULL != set) {
CFTypeRef context[2] = {rl, rls};
/* add new item to all common-modes */
// 則把set里的所有 mode 都執(zhí)行一遍 __CFRunLoopAddItemToCommonModes 函數(shù)
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
// 以上分支的邏輯就是,如果你往 kCFRunLoopCommonModes 里面添加一個 source,那么所有 _commonModes 里面的 mode 都會添加這個 source
} else {
// 根據(jù) modeName 查找 mode
CFRunLoopModeRef rlm = __CFRunLoopFIndMode(rl, modeName, true);
// 如果 _sources0 不存在,則初始化 _sources0 和 _portToV1SourceMap
if (NULL != rlm && NULL == rlm->_sources0) {
rlm->sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
rlm->sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBadks);
rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
}
// 如果 _sources0 和 _source1 中都不包含傳入的 source
if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
// 如果 version 是 0,則加到 _sources0
if (0 == rls->_context.version0.version) {
CFSetAddValue(rlm->_sources0, rls);
// 如果 version 是 1,則加到 _sources1
} else if (1 == rls->_context.version0.version) {
CFSetAddValue(rlm->_sources1, rls);
__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if (CFPORT_NULL != src_port) {
// 此處只有在加到 source1 的時候才會把 souce 和 一個 mach_port_t 對應(yīng)起來
// 可以理解為,source1 可以通過內(nèi)核向其端口發(fā)送消息來主動喚醒 runloop
CFDictionarySetValue(rlm->_portAToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
__CFPortSetInsert(scr_port, rlm->_portSet);
}
}
__CFRunLoopSourceLock(rls);
// 把 runloop 加入到 source 的 _runLoops 中
if (NULL == rls->_runLoops) {
rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks);
}
CFBagAddValue(rls->_runLoops, rl);
__CFRunLoopSourceUnlock(rls);
if (0 == rls->_context.version0.version) {
if (NULL != rls->_context.version0.schedule) {
doVer0Callout = true;
}
}
}
if (NULL != rlm) {
__CFRunLoopModeUnlock(rlm);
}
}
__CFRunLoopUnlock(rl);
if (doVer0Callout) {
// although it looses some protection for the source, we have no choice but
// to do this after unlocking the run loop and mode locks, to avoid deadlocks
// where the source wants to take a lock which is already held in another
// thread which is itself waiting for a run loop/mode lock
rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */
}
}
通過添加 source 的這段代碼可以得出如下結(jié)論:
- 如果 modeName 傳入 kCFRunLoopCommonModes, 則該 source 會被保存到 RunLoop 的 _commonModeItems 中
- 如果 modeName 傳入 kCFRunLoopCommonModes, 則該 source 會被添加到所有 commonMode 中
- 如果 modeName 傳入的不是 kCFRunLoopCommonModes,則會先查找該 Mode,如果沒有,會創(chuàng)建一個
- 同一個 source 在一個 mode 中只能被添加一次
CFRunLoopRemoveSource
remove 操作和 add 操作的邏輯基本一致,很容易理解。
//移除source
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
CHECK_FOR_FORK();
Boolean doVer0Callout = false, doRLSRelease = false;
__CFRunLoopLock(rl);
//如果是kCFRunLoopCommonModes,則從_commonModes的所有mode中移除該source
if (modeName == kCFRunLoopCommonModes) {
if (NULL != rl->_commonModeItems && CFSetContainsValue(rl->_commonModeItems, rls)) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
CFSetRemoveValue(rl->_commonModeItems, rls);
if (NULL != set) {
CFTypeRef context[2] = {rl, rls};
/* remove new item from all common-modes */
CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context);
CFRelease(set);
}
} else {
}
} else {
//根據(jù)modeName查找mode,如果不存在,返回NULL
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, false);
if (NULL != rlm && ((NULL != rlm->_sources0 && CFSetContainsValue(rlm->_sources0, rls)) || (NULL != rlm->_sources1 && CFSetContainsValue(rlm->_sources1, rls)))) {
CFRetain(rls);
//根據(jù)source版本做對應(yīng)的remove操作
if (1 == rls->_context.version0.version) {
__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if (CFPORT_NULL != src_port) {
CFDictionaryRemoveValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port);
__CFPortSetRemove(src_port, rlm->_portSet);
}
}
CFSetRemoveValue(rlm->_sources0, rls);
CFSetRemoveValue(rlm->_sources1, rls);
__CFRunLoopSourceLock(rls);
if (NULL != rls->_runLoops) {
CFBagRemoveValue(rls->_runLoops, rl);
}
__CFRunLoopSourceUnlock(rls);
if (0 == rls->_context.version0.version) {
if (NULL != rls->_context.version0.cancel) {
doVer0Callout = true;
}
}
doRLSRelease = true;
}
if (NULL != rlm) {
__CFRunLoopModeUnlock(rlm);
}
}
__CFRunLoopUnlock(rl);
if (doVer0Callout) {
// although it looses some protection for the source, we have no choice but
// to do this after unlocking the run loop and mode locks, to avoid deadlocks
// where the source wants to take a lock which is already held in another
// thread which is itself waiting for a run loop/mode lock
rls->_context.version0.cancel(rls->_context.version0.info, rl, modeName); /* CALLOUT */
}
if (doRLSRelease) CFRelease(rls);
}
添加 Observer 和 Timer
添加 observer 和 timer 的內(nèi)部邏輯和添加 source 大體類似。
區(qū)別在于 observer 和 timer 只能被添加到一個 RunLoop 的一個或者多個 mode 中,比如一個 timer 被添加到主線程的 RunLoop 中,則不能再把該 timer 添加到子線程的 RunLoop,而 source 沒有這個限制,不管是哪個 RunLoop,只要 mode 中沒有,就可以添加。
這個區(qū)別在文章最開始的結(jié)構(gòu)體中也可以發(fā)現(xiàn),CFRunLoopSource 結(jié)構(gòu)體中有保存 RunLoop 對象的數(shù)組,而 CFRunLoopObserver 和 CFRunLoopTimer 只有單個 RunLoop 對象。
RunLoop 運(yùn)行
在 Core Foundation 中我們可以通過以下 2 個 API 來讓 RunLoop 運(yùn)行:
void CFRunLoopRun(void)
在默認(rèn)的 mode 下運(yùn)行當(dāng)前線程的 RunLoopCFRunLoopRunResult CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
在指定 mode 下運(yùn)行當(dāng)前線程的 RunLoop
CFRunLoopRun
// 默認(rèn)運(yùn)行 runloop 的 kCFRunLoopDefaultMode
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
// 默認(rèn)在 KCFRunLoopDefaultMode 下運(yùn)行 runloop
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
在 CFRunLoopRun 函數(shù)中調(diào)用了 CFRunLoopRunSpecific 函數(shù),runloop 參數(shù)傳入當(dāng)前 RunLoop 對象,modeName 參數(shù)傳入 KCFRunLoopDefaultMode。驗(yàn)證了前面文檔的解釋。
CFRunLoopRunInMode
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
在 CFRunLoopRunInMode 函數(shù)中也調(diào)用了 CFRunLoopRunSpecific 函數(shù),runloop 參數(shù)傳入當(dāng)前 RunLoop 對象,modeName 參數(shù)繼續(xù)傳遞 CFRunLoopRunInMode 傳入的 modeName。也驗(yàn)證了前文文檔的解釋。
這里還可以看出,雖然 RunLoop 有很多個 mode,但是 RunLoop 在 run 的時候必須只能指定其中一個 mode,運(yùn)行起來后,被指定的 mode 即為 currentMode。
這 2 個函數(shù)都看不出來 RunLoop 是怎么 run 起來的。
接下來我們繼續(xù)探索一下 CFRunLoopRunSpecific 函數(shù)里面都干了什么,看看 RunLoop 具體是怎么 run 的。
CFRunLoopRunSpecific
* 指定 mode 運(yùn)行 runloop
* @param rl 當(dāng)前運(yùn)行的 runloop
* @param modeName 需要運(yùn)行的 mode 的 name
* @param seconds runloop 的超時時間
* @param returnAfterSourceHandled 是否處理完事件就返回
*/
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 根據(jù) modeName 找到本次運(yùn)行的 mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
// 如果沒有找到 || mode 中沒有注冊任何事情,則就此停止,不進(jìn)入循環(huán)
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource: kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
// 取上一次運(yùn)行的 mode
CFRunLoopModeRef previousMode = rl->_currentMode;
// 如果本次 mode 和上一次的 mode 一致
rl->_currentMode = currentMode;
// 初始化一個 result 為 kCFRunLoopRunFinished
int32_t result = kCFRunLoopRunFinished;
// 1.通知 observer 即將進(jìn)入 runloop
if (currentMode->_observerMask & kCFRunLoopEntry) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 10.通知 observer 已經(jīng)退出 runloop
if (currentMode->_observerMask & kCFRunLoopExit) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
通過 CFRunLoopRunSpecific 的內(nèi)部邏輯,我們可以得出:
- 如果指定一個不存在的 mode 來運(yùn)行 RunLoop,那么會失敗,mode 不會被創(chuàng)建,所有這里傳入的 mode 必須是存在的
- 如果指定了一個 mode,但是這個 mode 中不包含人 modeItem,那么 RunLoop 也不會運(yùn)行,所以必須要傳入至少包含一個 modeItem 的 mode
- 在進(jìn)入 run loop 之前通知 observer,狀態(tài)為 kCFRunLoopEntry
- 在退出 run loop 之后通知 observer,狀態(tài)為 kCFRunLoopExit
RunLoop 的運(yùn)行的最核心函數(shù)是 __CFRunLoopRun,接下來我們分析 __CFRunLoopRun 的源碼。
__CFRunLoopRun
這段代碼比較長,請做好心理準(zhǔn)備,我已經(jīng)加了比較詳細(xì)的注釋。本節(jié)開頭的 run loop 運(yùn)行步驟 2~9 步都在下面的代碼中得到驗(yàn)證。
/**
* 運(yùn)行run loop
*
* @param rl 運(yùn)行的RunLoop對象
* @param rlm 運(yùn)行的mode
* @param seconds run loop超時時間
* @param stopAfterHandle true:run loop處理完事件就退出 false:一直運(yùn)行直到超時或者被手動終止
* @param previousMode 上一次運(yùn)行的mode
*
* @return 返回4種狀態(tài)
*/
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
// 獲取系統(tǒng)啟動后的 CPU 運(yùn)行時間,用于控制超時時間
uint64_t startTSR = mach_absolute_time();
// 如果 RunLoop 或者 mode 是 stop 狀態(tài),則直接 return,不進(jìn)入循環(huán)
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm -> _stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
// mach 端口,在內(nèi)核中,消息在端口之間傳遞,初始為0
mach_port_name_t dispatchPort = MACH_PORT_NULL;
// 判斷是否為主線程
Boolen libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
// 如果在主線程 && runloop 是主線程的 runloop && 該 mode 是 commonMode,則給 mach 端口賦值為主線程收發(fā)消息的端口
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name))
dispatchPort = _dispatch_get_main_queue_port_4CF();
#if USE_DISPATCH_SOURCE_FOR_TIMERS
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue) {
// mode 賦值為 dispatch 端口 _dispatch_runloop_root_queue_perform_4CF
modeQueuePort = _dispatch_runloop_root_queue_get_port_4FC(rlm->_queue);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1)
}
}
#endif
// GCD 管理的定時器,用于實(shí)現(xiàn) runloop 的超時機(jī)制
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
// 立即超時
if (seconds <= 0.0) { // instant timeout
seconds = 0.0;
timeout_context->termTSR = 0ULL;
}
// seconds 為超時時間,超時執(zhí)行 __CFRunLoopTimeout 函數(shù)
else if (seconds <= TIMER_INTERVAL_LIMIT) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_UEUE_PRIOITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
dispatch_source_set_event_handler_f(timout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_IME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
}
// 永不超時
else {
seconds = 9999999999.0;
timeout_context->termTSR = UNT64_MAX;
}
// 標(biāo)志位默認(rèn)為 true
Boolean didDispatchPortLastTime = true;
// 記錄最后 runloop 狀態(tài),用于 return
int32_t reVal = 0;
do {
// 初始化一個存放內(nèi)核消息的緩沖地
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENAT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
match_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
HANDLE livePort = NULL;
Boolean windowsMessageReceived = false;
#endif
// 取所需要監(jiān)聽的 port
__CFPortSet waitSet = rlm->_portSet;
// 設(shè)置 RunLoop 為可以被喚醒狀態(tài)
__CFRunLoopUnsetIgnoreWakeUps(rl);
// 2.通知 observer,即將觸發(fā) timer 回調(diào),處理 timer 事件
if (rlm->_observerMask & kCFRunLoopBeforeTimes) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 3.通知 observer,即將觸發(fā) Source0 回調(diào)
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 執(zhí)行加入當(dāng)前 runloop 的 block
__CFRunLoopDoBlocks(rl, rlm);
// 4. 處理 source0 事件
// 有事件處理返回 true,沒有事件返回 false
Boolean sourceHandleThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
// 執(zhí)行加入當(dāng)前 runloop 的 block
__CFRunLoopDoBlocks(rl, rlm);
}
// 如果沒有 Sources0 事件處理并且沒有超市,poll 為 false
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 第一次 do...while 循環(huán)不會走該分支,因?yàn)?didDispatchPortLastTime 初始化是 true
if (MACH_PORT_NULL !+ dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
//從緩沖區(qū)讀取消息
msg = (mach_msg_header_t *)msg_buffer;
// 5. 接收 dispatchPort 端口消息,(接收 source1 事件)
if(__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
// 如果收到消息的話,前往第 9 步開始處理 msg
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
// 6. 通知觀察者 RunLoop 即將進(jìn)入休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 設(shè)置 RunLoop 為休眠zhuangt
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)
// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
// 這里有個內(nèi)循環(huán),用于接收等待端口的消息
// 進(jìn)入此循環(huán)后,線程進(jìn)入休眠,直到收到新消息才跳出循環(huán),繼續(xù)執(zhí)行 run loop
do {
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
// 7.接收 waitSet 端口的消息
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0: TIMEOUT_INFINTY);
// 收到消息之后,livePort 的值為 msg->msgh_local_port
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
break;
}
} while (1);
#else
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0: TIMEOUT_INFINITY);
#endif
#elif DEPLOYMENT_TARGET_WINDOWS
// hear, use the app-suplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0: TIMEOUT_INFINITY, rlm->_msgQMask, &windowsMessageRecieved);
#endif
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
// Must remove the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced. Also, we don't want them left
// in there if this function returns.
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
// 取消 runloop 的休眠狀態(tài)
__CFRunLoopUnsetSleeping(rl);
// 8.通知觀察者 runloop 被喚醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting))
__CFRunLoopDoObserver(rl, rlm, kCFRunLoopAfterWaiting);
// 9.處理收到的消息
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
#if DELOYMENT_TARGET_WINDOWS
if (windowsMessageReceived) {
// These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
if (rlm->_msgPump) {
rlm->_msgPump();
} else {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
// To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
// Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
// NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
__CFRunLoopSetSleeping(rl);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0 &livePort, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rml);
__CFRunLoopUnsetSleeping(rl);
}
#endif
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// 通過 CFRunLoopWake 喚醒
} else if (livePort == rl->_wakeUpPort) {
// 什么都不干,跳回 2 重新循環(huán)
#if DEPLOYMENT_TARGET_WINDOWS
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
// 如果是定時器事件
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
// 9.1 處理 timer 事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextATimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
// 如果是定時器事件
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
// 9.1 處理 timer 事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
// 如果是 dispatch 到 main queue 的 block
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
void *msg = 0;
#endif
// 9.2 執(zhí)行 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
// 有 source1 事件待處理
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
// 9.2 處理 source1 事件
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#elif DEPLOYMENTA_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
}
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
// 進(jìn)入 run loop 時傳入的參數(shù),處理完事件就返回
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
// run loop 超時
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
// run loop 被手動終止
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
// mode 被終止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
// mode 中沒有要處理的事件
retVal = kCFRunLoopRunFinished;
}
// 除了上面這幾種情況,都繼續(xù)循環(huán)
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
__CFRunLoopServiceMachPort
第7步調(diào)用了 __CFRunLoopServiceMachPort 函數(shù),這個函數(shù)在 run loop 中起到了至關(guān)重要的作用,下面給出了詳細(xì)注釋。
/**
* 接收指定內(nèi)核端口的消息
*
* @param port 接收消息的端口
* @param buffer 消息緩沖區(qū)
* @param buffer_size 消息緩沖區(qū)大小
* @param livePort 暫且理解為活動的端口,接收消息成功時候值為msg->msgh_local_port,超時時為MACH_PORT_NULL
* @param timeout 超時時間,單位是ms,如果超時,則RunLoop進(jìn)入休眠狀態(tài)
*
* @return 接收消息成功時返回true 其他情況返回false
*/
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout) {
Boolean originalBuffer = true;
kern_return_t ret = KERN_SUCCESS;
for (;;) { /* In that sleep of death what nightmares may come ... */
mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
msg->msgh_bits = 0; //消息頭的標(biāo)志位
msg->msgh_local_port = port; //源(發(fā)出的消息)或者目標(biāo)(接收的消息)
msg->msgh_remote_port = MACH_PORT_NULL; //目標(biāo)(發(fā)出的消息)或者源(接收的消息)
msg->msgh_size = buffer_size; //消息緩沖區(qū)大小,單位是字節(jié)
msg->msgh_id = 0; //唯一id
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
//通過mach_msg發(fā)送或者接收的消息都是指針,
//如果直接發(fā)送或者接收消息體,會頻繁進(jìn)行內(nèi)存復(fù)制,損耗性能
//所以XNU使用了單一內(nèi)核的方式來解決該問題,所有內(nèi)核組件都共享同一個地址空間,因此傳遞消息時候只需要傳遞消息的指針
ret = mach_msg(msg,
MACH_RCV_MSG|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV),
0,
msg->msgh_size,
port,
timeout,
MACH_PORT_NULL);
CFRUNLOOP_WAKEUP(ret);
//接收/發(fā)送消息成功,給livePort賦值為msgh_local_port
if (MACH_MSG_SUCCESS == ret) {
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
//MACH_RCV_TIMEOUT
//超出timeout時間沒有收到消息,返回MACH_RCV_TIMED_OUT
//此時釋放緩沖區(qū),把livePort賦值為MACH_PORT_NULL
if (MACH_RCV_TIMED_OUT == ret) {
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
//MACH_RCV_LARGE
//如果接收緩沖區(qū)太小,則將過大的消息放在隊(duì)列中,并且出錯返回MACH_RCV_TOO_LARGE,
//這種情況下,只返回消息頭,調(diào)用者可以分配更多的內(nèi)存
if (MACH_RCV_TOO_LARGE != ret) break;
//此處給buffer分配更大內(nèi)存
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}
小結(jié)
RunLoop 實(shí)際很簡單,它是一個對象,它和線程是一一對應(yīng)的,每個線程都有一個對應(yīng)的 RunLoop 對象,主線程的 RunLoop 會在程序啟動時自動創(chuàng)建,子線程需要手動獲取來創(chuàng)建。
RunLoop 運(yùn)行的核心是一個 do..while.. 循環(huán),遍歷所有需要處理的事件,如果有事件處理就讓線程工作,沒有事件處理則讓線程休眠,同時等待事件到來。