這一篇文章主要在於 Run Loop 源碼的閱讀,內容有點長,須要一些基礎。html
Run Loop 是一個 iOS 開發裏的基礎概念,它並不是獨有的機制,不少系統和框架都有相似的實現,Run Loop 是 Event Loop (事件循環)機制的在 iOS 平臺的一種實現。 查閱 wikipedia 有關 Event Loop 的描述:前端
在計算機科學裏, Event Loop / Run Loop 是一個用於等待和發送消息/事件的程序結構,在程序中等待和派發一系列事件或者消息。它經過向「事件提供者」發出請求來工做,一般會阻塞請求,直到事件到達,而後調用相應的事件處理程序。linux
說到 Event Loop ,其實還應該瞭解到 Event-Driven (事件驅動)。git
Event-Driven 的出現,在於解決圖形界面和用戶交互的問題:github
一般 GUI 程序的交互事件執行是由用戶來控制的,沒法預測它發生的節點,對應這樣的狀況,須要採用 Event-Driven 的編程方法。編程
Event-Driven 的實現原理,基本就是使用 Event Loop 完成。Event-Driven 程序的執行,能夠歸納成:windows
啓動 ——> 事件循環(即等待事件發生並處理之)。數組
在 GUI 的設計場景下,通常寫代碼會是下面的思惟:安全
用戶輸入 -> 事件響應 -> 代碼運行 -> 刷新頁面狀態bash
咱們一直在說 Event Loop 和 Event-Driven 。那什麼是 Event (事件) 呢?
在 Event-Driven 中,能夠把一切行爲都抽象爲 Event 。例如: IO 操做完成,用戶點擊按鈕,一個圖片加載完成,文本框的文字改變等等狀況,均可以看做是一個 Event 。
當 Event 被放到 Event Loop 裏進行處理的時候,會調用預先註冊過的代碼對 Event 作處理。這就叫 Event Handler 。
Event Handler 其實就是對 Event 的響應,能夠叫作事件回調,事件處理,事件響應,都是同樣的概念。
這裏須要注意的是,一個 Event 並不必定有 Event Handler .
通常來講,操做分爲同步和異步。
同步操做,是一個接一個的處理。等前一個處理完,再執行下一個。那麼在一些耗時任務上,好比有不少 I/O 操做 或者 網絡請求 的任務,線程就會有長時間在等待任務結果。
異步操做,是不用等待執行結果的,能夠直接在這期間執行另外的任務。等到任務結果出來以後,再進行處理。
實際上 Event Loop 就是實現異步的一種方法。
對於有回調的 Event,線程不用一直等待任務的結果出來再去執行下一個。而是等 Event 被加入到 Event Loop 時,再去執行。若是一個 Event 也沒有,那線程就會休眠,避免浪費資源。
若是沒有 Event Loop 來實現異步操做,那咱們的程序會很容易出現卡頓。
擴展 : JavaScript 在單線程條件下運行,能夠完成異步操做,也是基於 Event Loop 機制。 建議能夠參考 JavaScript異步編程 的內容來理解,更以幫助咱們舉一反三,學習到通用的知識。
網上目前有關 Run Loop 的文章, 10 篇裏面可能有 8 篇都是重複了 深刻理解RunLoop 中的代碼。
然而這都是通過做者大量簡化過的版本,隱藏了大量的細節。
其實從細節裏面,咱們同樣能夠學習到不少東西,不妨嘗試去閱讀一下。
咱們知道 CFRunLoopRef 的代碼是開源的,能夠查看源代碼來看它的實現,我選擇的版本是 CF-1153.18 中的 CFRunLoop.c 。
因爲蘋果不容許咱們直接建立 RunLoop,只提供 2 個獲取操做的函數:
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;
}
複製代碼
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
複製代碼
在兩個函數裏,都有使用了 CHECK_FOR_FORK() 。
它應該是屬於多進程狀況下的一個斷言。
Threading Programming Guide 中,有這麼一段話:
Warning: When launching separate processes using the fork function, you must always follow a call to fork with a call to exec or a similar function. Applications that depend on the Core Foundation, Cocoa, or Core Data frameworks (either explicitly or implicitly) must make a subsequent call to an exec function or those frameworks may behave improperly.
也就是說,當經過 fork 啓動一個新進程的時候,你必需要接着調用一個 exec 或相似的函數。而依賴於 Core Founadtion / Cocoa / Core Data 框架的應用,必須調用 exec 函數,不然這些框架也許不能正確的工做。
因此爲了保證安全,使用 CHECK_FOR_FORK 進行檢查。
這裏簡單提一下 fork 。
在 UNIX 中,用 fork 來建立子進程,調用 fork( ) 的進程被稱爲父進程,新進程是子進程,而且幾乎是父進程的徹底複製(變量、文件句柄、共享內存消息等相同,但 process id 不一樣)。
由於子進程和父進程基本是同樣的,要想讓子進程去執行其餘不一樣的程序,子進程就須要調用 exec ,把自身替換爲新的進程,其中process id不變,但原來進程的代碼段、堆棧段、數據段被新的內容取代,來執行新的程序。
這樣 fork 和 exec 就成爲一種組合。
而在 iOS 這樣的類 UNIX 系統裏,基本上也都要經過 fork 的形式來建立新的進程。
假如沒有執行完 exec ,那麼執行的代碼段等內容,仍是父進程裏的,出現問題能夠說百分之百。這就是 CHECK_FOR_FORK 檢查的目的。
爲了幫助理解,還須要先說 Thread-specific data (TSD),它能夠叫做 線程私有數據 , 這個概念來自於 unix 之中。
它是存儲和查詢與某個線程相關數據的一種機制:
進程內的全部線程,共享進程的數據空間,所以全局變量爲全部線程所共有。 而有時線程也須要保存本身的私有數據,這時能夠建立線程私有數據(Thread-specific Data)TSD來解決。 在線程內部,私有數據能夠被各個函數訪問,但對其餘線程是屏蔽的。例如咱們常見的變量errno,它返回標準的出錯信息。它顯然不能是一個局部變量,幾乎每一個函數都應該能夠調用它;但它又不能是一個全局變量。
在 Pthreads 裏,把它叫作 Thread-local storage (線程私有存儲) , 有如下幾個相關的操做函數:
- pthread_key_create(): 分配用於標識進程中線程特定數據的pthread_key_t類型的鍵
- pthread_key_delete(): 銷燬現有線程特定數據鍵
- pthread_setspecific(): 爲指定線程的特定數據鍵設置綁定的值
- pthread_getspecific(): 獲取調用線程的鍵綁定值,並將該綁定存儲在 value 指向的位置中
複製代碼
在蘋果的平臺上,基本就是利用上面操做實現 TSD 。
RunLoop 實際上就屬於 TSD 的裏存儲的一種數據。因此咱們講, RunLoop 和線程是一一對應的。而 RunLoop 會在線程銷燬時,跟着一塊兒清理,也是因爲線程私有數據的機制。
TSD 對應存儲的 key 有相關的析構函數,線程退出時,析構函數函數就會按照操做系統,實現定義的順序被調用。因此在 CFSetTSD 會有一個析構函數的參數位置。
關於 CFGetTSD / CFSetTSD , 在 ForFoundationOnly.h 找到定義:
// ---- Thread-specific data --------------------------------------------
// Get some thread specific data from a pre-assigned slot.
CF_EXPORT void *_CFGetTSD(uint32_t slot);
// Set some thread specific data in a pre-assigned slot. Don't pick a random value. Make sure you're using a slot that is unique. Pass in a destructor to free this data, or NULL if none is needed. Unlike pthread TSD, the destructor is per-thread.
CF_EXPORT void *_CFSetTSD(uint32_t slot, void *newVal, void (*destructor)(void *));
複製代碼
TSD
也就是 thread specific data
的縮寫了。
按照註釋,說明 CFGetTSD
的做用是 -- 從預先賦值的位置,獲得 TSD
。
上面也說明了 CFSetTSD
的做用 -- 在預先位置設置 TSD
。 這個數據不能夠是隨機的值,並保證你使用的位置有惟一性。若是須要釋放這個數據,就傳入析構函數;若是不須要釋放,則傳入NULL。和 pthread TSD 不一樣的是,這一個析構函數是每個線程都有的。
在上面的CFRunLoopGetCurrent
裏,是這麼使用 _CFGetTSD 的:
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
複製代碼
這裏的 slot 值,通常對應的,都應該是相似 __CFTSDKeyRunLoop 的枚舉類型的關鍵字。
在 CFPlatform.c 找到 CFGetTSD / CFSetTSD 具體的實現,發現二者其實都依靠了 CFTSDTable 類型的一個 table 實現。
CFTSDTable 是一個保存 TSD 數據的結構體:
// Data structure to hold TSD data, cleanup functions for each
typedef struct __CFTSDTable {
uint32_t destructorCount;
uintptr_t data[CF_TSD_MAX_SLOTS];
tsdDestructor destructors[CF_TSD_MAX_SLOTS];
} __CFTSDTable;
複製代碼
它擁有兩個數組: data 存儲私有數據, destructors 存儲釋放函數 . 還有一個 destructorCount ,它顧名思義就是 destructors 數組的數量。
CFGetTSD 主要是取了 table ,獲取 table 的 data 數組,按 slot 索引取值。
CFSetTSD 的做用,就是根據 CFTSDTable 的結構,分別是往 table 裏設置 data 數組 slot 位置的值,以及 destructors 數組 slot 位置的值:
void *oldVal = (void *)table->data[slot];
table->data[slot] = (uintptr_t)newVal;
table->destructors[slot] = destructor;
複製代碼
不管是 CFRunLoopGetMain 仍是 CFRunLoopGetCurrent ,二者調用了 CFRunLoopGet0 :
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {// kNilPthreadT 是一個靜態變量爲 0
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
複製代碼
這裏對主要的流程作一下解釋:
注意:
在瞭解完 CFSetTSD 的做用, CFTSDKeyRunLoop 設置的意思就很清楚: 在 CFTSDKeyRunLoop 位置,存儲 loop , 但不對 loop 設置析構函數。
直接對於 loop 的設置,其實這裏已經完成了。
網絡文章大部分,直接就說在 CFTSDKeyRunLoopCntr 設置了清理 loop 的回調。
對於爲何能夠釋放 loop ,卻避而不談。
你們想過沒有 :
在 CFTSDKeyRunLoopCntr 位置,給出的參數是
PTHREAD_DESTRUCTOR_ITERATIONS - 1
PTHREAD_DESTRUCTOR_ITERATIONS 表示的,是線程退出時,操做系統實現試圖銷燬線程私有數據的最大次數。試圖銷燬次數,和 CFFinalizeRunLoop 這個析構函數,是怎麼關聯起來的?又是怎麼被調用的?
在前面,說過了 CFTSDTable ,實際上在 CFTSDGetTable() 裏面,就作了相關 TSD 的設置:
// Get or initialize a thread local storage. It is created on demand.
static __CFTSDTable *__CFTSDGetTable() {
...
pthread_key_init_np(CF_TSD_KEY, __CFTSDFinalize);
...
}
複製代碼
經過 CF_TSD_KEY ,指定了對應的析構函數 CFTSDFinalize 。
而 CFTSDFinalize 的代碼以下:
static void __CFTSDFinalize(void *arg) {
// Set our TSD so we're called again by pthreads. It will call the destructor PTHREAD_DESTRUCTOR_ITERATIONS times as long as a value is set in the thread specific data. We handle each case below.
__CFTSDSetSpecific(arg);
if (!arg || arg == CF_TSD_BAD_PTR) {
// We've already been destroyed. The call above set the bad pointer again. Now we just return.
return;
}
// On first calls invoke destructor. Later we destroy the data.
// Note that invocation of the destructor may cause a value to be set again in the per-thread data slots. The destructor count and destructors are preserved.
// This logic is basically the same as what pthreads does. We just skip the 'created' flag.
for (int32_t i = 0; i < CF_TSD_MAX_SLOTS; i++) {
if (table->data[i] && table->destructors[i]) {
uintptr_t old = table->data[i];
table->data[i] = (uintptr_t)NULL;
table->destructors[i]((void *)(old));
}
}
if (table->destructorCount == PTHREAD_DESTRUCTOR_ITERATIONS - 1) { // On PTHREAD_DESTRUCTOR_ITERATIONS-1 call, destroy our data
free(table);
// Now if the destructor is called again we will take the shortcut at the beginning of this function.
__CFTSDSetSpecific(CF_TSD_BAD_PTR);
return;
}
}
複製代碼
咱們能夠看到,table 會循環遍歷 data 和 destructors 的數據,而且把 old 變量做爲 destructors 裏函數的參數。
這就是線程退出時,會調用到 Run Loop 銷燬函數的緣由。
同時也因爲 table 是從 0 開始遍歷,因此會根據枚舉值的大小,來決定銷燬調用的順序的。
咱們能夠在 CFInternal.h 中找到相關枚舉定義:
// Foundation uses 20-40
// Foundation knows about the value of __CFTSDKeyAutoreleaseData1
enum {
__CFTSDKeyAllocator = 1,
__CFTSDKeyIsInCFLog = 2,
__CFTSDKeyIsInNSCache = 3,
__CFTSDKeyIsInGCDMainQ = 4,
__CFTSDKeyICUConverter = 7,
__CFTSDKeyCollatorLocale = 8,
__CFTSDKeyCollatorUCollator = 9,
__CFTSDKeyRunLoop = 10,
__CFTSDKeyRunLoopCntr = 11,
__CFTSDKeyMachMessageBoost = 12, // valid only in the context of a CFMachPort callout
__CFTSDKeyMachMessageHasVoucher = 13,
// autorelease pool stuff must be higher than run loop constants
__CFTSDKeyAutoreleaseData2 = 61,
__CFTSDKeyAutoreleaseData1 = 62,
__CFTSDKeyExceptionData = 63,
};
複製代碼
註釋裏有一句 autorelease pool stuff must be higher than run loop constants
的說明,這一點就其實關係到 Run Loop 和 autorelease pool 釋放的順序了。
/*! @abstract Compare and swap for <code>int</code> values. @discussion This function compares the value in <code>__oldValue</code> to the value in the memory location referenced by <code>__theValue</code>. If the values match, this function stores the value from <code>__newValue</code> into that memory location atomically. This function is equivalent to {@link OSAtomicCompareAndSwap32}. @result Returns TRUE on a match, FALSE otherwise. */
OSATOMIC_DEPRECATED_REPLACE_WITH(atomic_compare_exchange_strong)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0)
bool OSAtomicCompareAndSwapInt( int __oldValue, int __newValue, volatile int *__theValue );
複製代碼
這一個函數,它首先對 oldValue , theValue 進行比較.
若是兩個值相等,就執行 theValue = newValue,並返回 YES.
若是兩個值不等,返回 NO .
值得注意的是,這個函數引入了 barrier,它的操做是原子的。 這是一個典型的 CAS 操做,無獨有偶,在 RAC 中的一個特色也是使用 Atomic Operations ,完成線程同步。
它在CFRunLoopGet0
的做用是 : 比較 CFRunLoops 是否爲 null 。 若是爲 null (第一次建立)了,就把 dict 賦值給 CFRunLoops 。若是不爲 null,就釋放掉 dict 。
這裏再稍微提一下 barrier , 上面說它保證了原子操做。
memory barrier 在維基的定義是:
內存屏障(英語:Memory barrier),也稱內存柵欄,內存柵障,屏障指令等,是一類同步屏障指令,是CPU或編譯器在對內存隨機訪問的操做中的一個同步點,使得此點以前的全部讀寫操做都執行後才能夠開始執行此點以後的操做。 大多數現代計算機爲了提升性能而採起亂序執行,這使得內存屏障成爲必須。
而對於 Objective C 的實現來講,幾乎全部的加鎖操做最後都會設置 memory barrier ,官方文檔的解釋:
Note: Most types of locks also incorporate a memory barrier to ensure that any preceding load and store instructions are completed before entering the critical section.
爲了防止編譯器對咱們的代碼作優化,改變咱們代碼的指令順序,能夠採用 barrier 設置對咱們的代碼順序作保證。
講完 Run Loop 怎麼獲取,再看 Run Loop 怎麼建立。
對於 CFRunLoopCreate :
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
CFRunLoopRef loop = NULL;
CFRunLoopModeRef rlm;
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
if (NULL == loop) {
return NULL;
}
(void)__CFRunLoopPushPerRunData(loop);
__CFRunLoopLockInit(&loop->_lock);
loop->_wakeUpPort = __CFPortAllocate();
if (CFPORT_NULL == loop->_wakeUpPort) HALT;
__CFRunLoopSetIgnoreWakeUps(loop);
loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
loop->_commonModeItems = NULL;
loop->_currentMode = NULL;
loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
loop->_blocks_head = NULL;
loop->_blocks_tail = NULL;
loop->_counterpart = NULL;
loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
loop->_winthread = GetCurrentThreadId();
#else
loop->_winthread = 0;
#endif
rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
return loop;
}
複製代碼
大致說一下建立的流程:
注意: CFRunLoopPushPerRunData 會在建立時作一些初始化設置, __CFPortAllocate() 會設置喚醒的端口, CFRunLoopSetIgnoreWakeUps 調用的緣由時,目前處於喚醒狀態,對它的消息作忽略。 HALT 命令能夠中止系統運行,假如 wakeUpPort 爲 CFPORT_NULL
真正建立獲得 CFRunLoopRef 類型的 loop ,調用的是 CFRuntimeCreateInstance
來建立的。
它是一個用來建立 CF 實例類型的函數:
CF_EXPORT CFTypeRef _CFRuntimeCreateInstance(CFAllocatorRef allocator, CFTypeID typeID, CFIndex extraBytes, unsigned char *category);
複製代碼
更具體的解釋能夠查看 CFRuntime.h 對它的定義。
CFRunLoopCreate 給它傳入了一個默認的分配器 kCFAllocatorSystemDefault ,一個 CFRunLoopGetTypeID() ,一個 size 。
CFRunLoopGetTypeID() 的操做以下:
CFTypeID CFRunLoopGetTypeID(void) {
static dispatch_once_t initOnce;
dispatch_once(&initOnce, ^{
__kCFRunLoopTypeID = _CFRuntimeRegisterClass(&__CFRunLoopClass);
__kCFRunLoopModeTypeID = _CFRuntimeRegisterClass(&__CFRunLoopModeClass);
});
return __kCFRunLoopTypeID;
}
複製代碼
它在裏面註冊了 CFRunLoopClass 和 CFRunLoopModeClass 的類型,並用返回值,給對應的 typeID 賦值。做爲單例,只運行一次。
size 的計算爲 :
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
複製代碼
size 是一個 CFRunLoop 類型自己的大小,減掉 CFRuntimeBase 類型的大小獲得的結果。
爲何要減去一個 CFRuntimeBase 的類型大小?
查看 CFRuntime.c 對源碼,發現裏面會把減掉的 sizeof(CFRuntimeBase) 再給加回來:
CFIndex size = sizeof(CFRuntimeBase) + extraBytes + (usesSystemDefaultAllocator ? 0 : sizeof(CFAllocatorRef));
複製代碼
static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create)
複製代碼
CFRunLoopFindMode 是一個用來查找 mode 的函數,同時也能夠來建立 mode 。
它其中有利用兩個宏,來對 timer 的種類進行判斷.查閱了一下定義:
#if DEPLOYMENT_TARGET_MACOSX
#define USE_DISPATCH_SOURCE_FOR_TIMERS 1
#define USE_MK_TIMER_TOO 1
#else
#define USE_DISPATCH_SOURCE_FOR_TIMERS 0
#define USE_MK_TIMER_TOO 1
#endif
複製代碼
也就是說,在 MACOSX 下,同時還會有使用 dispatch timer 來作定時器。而 MK_TIMER 是兩個平臺下都有的。
函數的大致邏輯是先判斷有無,有就返回. 沒有的話,就根據 create 的值決定是否新建立一個 mode .
在 CFRunLoopCreate 裏面,調用的代碼是
CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true)
複製代碼
它會新建立一個 mode 返回。
啓動 Run Loop 有 2 個函數,一個是 CFRunLoopRun
, 一個是 CFRunLoopRunInMode
:
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
複製代碼
注:1.0e10,這個表示1.0乘以10的10次方,這個參數主要是規定RunLoop的時間,傳這個時間,表示線程常駐。
主線程的RunLoop調用函數,就是使用了 CFRunLoopRun
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
複製代碼
查看上面兩個啓動 Run Loop 運行的函數實現,發現都使用了 CFRunLoopRunSpecific
.
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
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);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
複製代碼
1.經過 runloop 的 modeName 查找當前 mode。由於 CFRunLoopFindMode 的 create 參數爲 false , 若是沒找到,直接爲 null ,不會建立新的 mode.
2.若是當前 mode 爲空,函數結束,返回 CFRunLoopRunFinished .
這裏比較奇怪的是
Boolean did = false
直接寫死了 did 的值,後面又是return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished
. 懷疑 did 的值,應該還有一段代碼是決定kCFRunLoopRunHandledSource
的結果,被蘋果隱藏了沒有開源出來。
3.若是當前 mode 存在,作一些賦值操做 .
4.向觀察者發送 kCFRunLoopEntry 的消息,即將進入 RunLoop .
5.進入 CFRunLoopRun 函數,在這裏作一系列觀察和操做。
6.向觀察者發送 kCFRunLoopExit 的消息,即將退出 RunLoop .
關於 CFRunLoopRun 在運行的時候,有 4 個枚舉值表示它的狀態:
/* Reasons for CFRunLoopRunInMode() to Return */
enum {
kCFRunLoopRunFinished = 1,//結束
kCFRunLoopRunStopped = 2,//暫停
kCFRunLoopRunTimedOut = 3,//超時
kCFRunLoopRunHandledSource = 4 //執行事件
};
複製代碼
CFRunLoopDoObservers 的官方文檔說明以下:
A CFRunLoopObserver provides a general means to receive callbacks at different points within a running run loop. In contrast to sources, which fire when an asynchronous event occurs, and timers, which fire when a particular time passes, observers fire at special locations within the execution of the run loop, such as before sources are processed or before the run loop goes to sleep, waiting for an event to occur. Observers can be either one-time events or repeated every time through the run loop’s loop.
Each run loop observer can be registered in only one run loop at a time, although it can be added to multiple run loop modes within that run loop.
一個 CFRunLoopObserver 提供了一個通用的方法,在不一樣的時機去接受運行中的 runloop 的回調。與在一個異步事件發生時觸發的源,和在特定時間以後觸發的定時器相比,在 run loop 執行的過程當中, 觀察者會在特定的位置發送信號,例如 sources 執行以前活着 run loop 將要休眠以前,等待事件的發生. 觀察者能夠是一次性的,或者在經過每次 run loop 的循環裏重複。
每一個 run loop 觀察者只能在 run loop 中註冊一次,儘管它能夠添加到該 run loop 內的多個 run loop mode 中。
這裏其實有兩個 CFRunLoopRun 函數,一個是暴露給咱們在外面使用的,不帶參數的:
CF_EXPORT void CFRunLoopRun(void);
複製代碼
如今要說的,是這一個:
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) __attribute__((noinline));
複製代碼
由於函數比較長,因此分段來進行講解.
1.runloop 狀態判斷 / GCD 隊列的端口設置:
//獲取開始時間
uint64_t startTSR = mach_absolute_time();
//對 runloop 狀態作判斷,檢查是否處於 stop 的狀況
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
//聲明 dispatchPort 變量,做爲一個 mach_port 通訊的端口,初始化值爲 MACH_PORT_NULL
mach_port_name_t dispatchPort = MACH_PORT_NULL;
// 檢測是否在主線程 && ( (是隊列發的消息&&mode爲null)||(不是隊列發的消息&&不在主隊列))
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
//若是是隊列安全的,而且是主線程runloop,設置它對應的通訊端口
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
複製代碼
#define HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY 0
複製代碼
這裏對於它的定義是 0 ,寫死了。我猜想應該仍是有一個函數去作判斷的。
目前只能從字面意思猜想,表明是否只分發 dispatch 消息的
#define _dispatch_get_main_queue_port_4CF _dispatch_get_main_queue_handle_4CF
複製代碼
它是 dispatch_get_main_queue_handle_4CF 的宏,存在 libdispatch 中,裏面對它的實現爲:
dispatch_runloop_handle_t
_dispatch_get_main_queue_handle_4CF(void)
{
dispatch_queue_t dq = &_dispatch_main_q;
dispatch_once_f(&_dispatch_main_q_handle_pred, dq,
_dispatch_runloop_queue_handle_init);
return _dispatch_runloop_queue_get_handle(dq);
}
複製代碼
返回的是主線程 runloop 所關聯的的端口。
2.MACOSX 下,聲明一個 mode 的隊列通訊端口(在 MACOSX 環境中):
#if USE_DISPATCH_SOURCE_FOR_TIMERS
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue) {
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
複製代碼
3.根據超時 seconds 的時長,作對應操做。
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
//小於等於 0 ,片刻的超時(instant timeout),直接設置爲 0 ,不超時
if (seconds <= 0.0) {
seconds = 0.0;
timeout_context->termTSR = 0ULL;
} else if (seconds <= TIMER_INTERVAL_LIMIT) {//在限制的超時間隔內
//根據是否爲主線程,設置隊列是主隊列仍是後臺隊列
dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
//建立一個 GCD Timer
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);
//把 timer 和 context 給關聯起來
dispatch_set_context(timeout_timer, timeout_context);
dispatch_source_set_event_handler_f(timeout_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_TIME_FOREVER, 1000ULL);
//恢復喚起 timer 執行
dispatch_resume(timeout_timer);
} else {
//無限期超時
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
複製代碼
4.進入 do - while 循環,直到 reVal 不爲 0 。如下代碼爲更好理解,刪去 windows 相關:
// 設置判斷是否爲最後一次 dispatch 的端口通訊的變量
Boolean didDispatchPortLastTime = true;
// 設置一個結果變量,最後爲幾個 CFRunLoopRunInMode 裏返回狀態之一。
int32_t retVal = 0;
do {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
// 一個狀態變量,用於 消息狀態 標誌,初始值爲 UNCHAMGED
voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
voucher_t voucherCopy = NULL;
#endif
//聲明一個 msg_buffer 數組
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
//聲明和 mach port 有關的 port 和 msg 變量
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
// 聲明一個類型爲 CFPortSet 的 waitSet, 值爲 run loop mode 裏的 portSet.
__CFPortSet waitSet = rlm->_portSet;
//將 run loop 從忽略喚醒消息的狀態 unset ,開始接受喚醒消息
__CFRunLoopUnsetIgnoreWakeUps(rl);
// 2. 通知 observers , Run Loop 即將觸發 Timer 回調。
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 3. 通知 observers , Run Loop 即將觸發 Source0 (非 mach port) 回調。
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 執行 block
__CFRunLoopDoBlocks(rl, rlm);
// 4. 執行 Source0 (非 mach port) 。
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {// 執行完 source0 後,假如還有須要執行的,再執行一次 block
__CFRunLoopDoBlocks(rl, rlm);
}
// poll 變量,是否處理 source 或未超時
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
msg = (mach_msg_header_t *)msg_buffer;
// 5. 若是有 Source1 (基於port) 處於 ready 狀態
// 直接處理這個 Source1 而後跳轉去處理消息(handle_msg)。
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
}
didDispatchPortLastTime = false;
// 6.通知 Observers: RunLoop 的線程即將進入休眠(sleep)。
// 注意到若是實際處理了 source0 或者超時,不會進入睡眠,因此不會通知
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 設置標誌位, Run Loop 休眠
__CFRunLoopSetSleeping(rl);
// 使用 GCD 的話,將 GCD 端口加入監聽端口集合中
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
// 休眠開始的時間,根據 poll 狀態決定爲 0 或者當前的絕對時間
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
// 7. 經過 CFRunLoopServiceMachPort 調用 mach_msg 休眠,等待被 mach_msg 消息喚醒
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
// 若是在 MACOSX 中
#if USE_DISPATCH_SOURCE_FOR_TIMERS
// 處理 GCD timer
do {
if (kCFUseCollectableAllocator) {//假若有kCFUseCollectableAllocator分配器,使用 memset 清空msg_buffer
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
// 設置 mach port 通訊,會睡眠線程
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// modeQueue 存在,並且爲 livePort
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.
//執行 run loop mode 裏的隊列,直到隊列都執行完成
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {//假如 _timerFired 爲真,把 livePort 做爲隊列端口,在以前服務於 timers
rlm->_timerFired = false;
//退出
break;
} else {// _timerFired 爲假, 而且 msg 存在不爲 msg_buffer, 釋放 msg
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
//退出
break;
}
} while (1);
#else // 不在 MACOSX 中
if (kCFUseCollectableAllocator) {//若是 kCFUseCollectableAllocator 分配器,使用 memset 清空 msg_buffer
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
//CFRunLoopServiceMachPort 會讓線程休眠
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
//上鎖
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
// 根據 poll 的值,記錄休眠時間
rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
//對 waitSet 裏的 dispatchPort 端口作移除
__CFPortSetRemove(dispatchPort, waitSet);
//讓 Run Loop 忽略喚醒消息,由於已經從新在運行了
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
// 8. 通知 observers: kCFRunLoopAfterWaiting, 線程剛被喚醒
// 注意實際處理過 source 0 或者已經超時的話,不會通知(由於沒有睡)
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//處理對應喚醒的消息
handle_msg:;
//將 Run Loop 從新忽略喚醒消息,由於已經從新在運行了
__CFRunLoopSetIgnoreWakeUps(rl);
if (MACH_PORT_NULL == livePort) {// livePort 爲空,什麼事都不作
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) {// livePort 等於 run loop 的 _wakeUpPort
// 被 CFRunLoopWakeUp 函數喚醒的
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
}
// 在 MACOSX 裏
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {//livePort 等於 modeQueuePort
//9.1-1 被 timers 喚醒,處理 timers
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {//livePort 等於 run loop mode 的 _timerPort
// 9.1-2 被 timers 喚醒,處理 timers
CFRUNLOOP_WAKEUP_FOR_TIMER();
// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
else if (livePort == dispatchPort) {// livePort 等於 dispatchPort
// 9.2 若是有dispatch到main_queue的block,執行block
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
//解鎖
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
//設置 CFTSDKeyIsInGCDMainQ 位置的 TSD 爲 6 .
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
// 處理 msg
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
//設置 CFTSDKeyIsInGCDMainQ 位置的 TSD 爲 0.
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
//上鎖
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
//設置變量
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
//9.3 被 source (基於 mach port) 喚醒
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// 假如咱們 從這個 mach_msg 中接收到一個 voucher,而後在 TSD 中放置一個複製的新的 voucher.
// CFMachPortBoost 會在 TSD 中去查找這個 voucher.
// 經過使用 TSD 中的值,咱們將 CFMachPortBoost 綁定到這個接收到的 mach_msg 中,在這兩段代碼之間沒有任何機會再次設置憑證
voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {//若是 rls 存在
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
//處理 Source ,並返回執行結果
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {//發送reply消息(假如 reply 不爲空)
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
//釋放 reply 變量
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
}
// Restore the previous voucher
_CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
// 執行加入到Loop的block
__CFRunLoopDoBlocks(rl, rlm);
//根據一次循環後的狀態,給 retVal 賦值 。狀態不變則繼續循環
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
// 循環一次後收尾處理
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
#endif
} while (0 == retVal);
複製代碼
上面的代碼,有幾個地方的定義可能須要結合其它地方纔能理解:
/*! * @typedef voucher_mach_msg_state_t * * @abstract * Opaque object encapsulating state changed by voucher_mach_msg_adopt(). */
typedef struct voucher_mach_msg_state_s *voucher_mach_msg_state_t;
複製代碼
不透明的對象封裝狀態由 voucher_mach_msg_adopt() 改變,它表明一種 mach_msg 通訊時的狀態。
DISPATCH_EXPORT HANDLE _dispatch_get_main_queue_handle_4CF(void);
複製代碼
返回做爲主隊列相關聯的 run loop 。
5.釋放 timerout_timer 定時器相關
if (timeout_timer) {//若是存在,取消並釋放
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {//不存在,將對應的 timeour_context 釋放
free(timeout_context);
}
//結束返回 retVal 狀態。
return retVal;
複製代碼
這個函數是讓線程休眠的關鍵,它在裏面作了和 mach port 相關的操做。
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, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
Boolean originalBuffer = true;
kern_return_t ret = KERN_SUCCESS;
for (;;) { /* In that sleep of death what nightmares may come ... */
// msg 相關數據設置
mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
msg->msgh_bits = 0;
msg->msgh_local_port = port;
msg->msgh_remote_port = MACH_PORT_NULL;
msg->msgh_size = buffer_size;
msg->msgh_id = 0;
// 根據 timeout 的值,決定 Run Loop 是休眠仍是執行
// timeout 爲 TIMEOUT_INFINITY 時,才執行 CFRUNLOOP_SLEEP() 休眠
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
// 發送並接收 mach port 消息
ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|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);
// 在 mach_msg 以後注意全部 voucher 相關的正常運行
// 假如咱們沒有釋放前面的 voucher , 將會出現內存泄漏
voucher_mach_msg_revert(*voucherState);
// 會有調用者去負責調用 voucher_mach_msg_revert .它會讓接收到的 voucher 變成當前的這一個值。
*voucherState = voucher_mach_msg_adopt(msg);
if (voucherCopy) {
if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
// 調用者 在這裏 請求了一個 voucher 的複製的值。經過在 mach_msg 先後作這個操做,咱們確保在 mach_msg 返回和使用 voucher 的複製值的時候,沒有涉及設置 voucher 的值。
// 爲確保 CFMachPortBoost 使用的 voucher ,因此咱們只在 voucher 不是 state_unchanged 的時候,去設置 TSD 。
*voucherCopy = voucher_copy();
} else {
//值爲 VOUCHER_MACH_MSG_STATE_UNCHANGED ,置爲 null
*voucherCopy = NULL;
}
}
// 喚醒 Run Loop
CFRUNLOOP_WAKEUP(ret);
if (MACH_MSG_SUCCESS == ret) {// ret 成功後,設置 livePort 的值,返回 true
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
if (MACH_RCV_TIMED_OUT == ret) {// ret 超時,釋放 msg ,有關變量置空,返回 false
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
//ret 不爲 MACH_RCV_TOO_LARGE ,退出循環
if (MACH_RCV_TOO_LARGE != ret) break;
//ret 爲 MACH_RCV_TOO_LARGE,作釋放操做,從新進入循環
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}
複製代碼
這裏有查詢 CFRUNLOOP_SLEEP() 和 CFRUNLOOP_POLL() 等函數,都是 do { } while (0)
這樣的宏,沒有真正實現代碼,因此沒法再看到具體的狀況。
這一次學習的過程,最大的感觸,就是對於知識的相通性。
例如對於 TSD 線程私有數據的理解,搜尋不少跟 iOS 有關資料都找不到說明,最後是在 unix 相關的文章纔看到解釋。還有 Event Loop 的機制在其它平臺等實現。
固然整個過程比較枯燥,閱讀的量也比較大,須要耐心。
比較遺憾的是,有一些地方,蘋果並無給出具體的代碼實現或者明確的解釋。
本人水平有限,若有錯誤和值得商榷的地方,歡迎你們拍磚。