RunLoop這個名詞對于iOS開發(fā)來說應該是一個聽膩了的詞匯,而且只知其一不知其二,本篇章就來再深入復習一下RunLoop
RunLoop簡介
什么是RunLoop
一般來講,一個線程一次只能執(zhí)行一個任務,執(zhí)行完成后線程就會退出。如果我們需要一個機制,讓線程能隨時處理事件但并不退出,這種模型通常被稱作 Event Loop。 Event Loop 在很多系統(tǒng)和框架里都有實現(xiàn),比如 Node.js 的事件處理,比如 Windows 程序的消息循環(huán),再比如 OSX/iOS 里的 RunLoop。
實現(xiàn)這種模型的關鍵點在于:管理事件/消息,讓線程在沒有處理消息時休眠以避免資源占用、在有消息到來時立刻被喚醒。
RunLoop作用
- 保持程序持續(xù)運行:程序一啟動就會開一個主線程,主線程一開起來就會跑一個主線程對應的RunLoop,RunLoop保證主線程不會被銷毀,也就保證了程序的持續(xù)運行
- 處理App中的各種事件(比如:觸摸事件,定時器事件,Selector事件等)
- 節(jié)省CPU資源,提高程序性能:程序運行起來時,當什么操作都沒有做的時候,RunLoop就告訴CPU,現(xiàn)在沒有事情做,我要去休息,這時CPU就會將其資源釋放出來去做其他的事情,當有事情做的時候RunLoop就會立馬起來去做事情
我們先通過API內(nèi)一張圖片來簡單看一下RunLoop內(nèi)部運行原理

為什么使用RunLoop
了解了RunLoop的作用,那么在蘋果系統(tǒng)中,為什么使用RunLoop呢?主要有一下幾點
- 使程序一直運行,并接受用戶輸入
- 決定程序在何時處理應該處理哪些Event
- 調(diào)用解耦(Message Queue): 比如一次滑屏事件,可能會觸發(fā)多條消息,所以必須有一個類似Message Queue的模塊去處理來解耦,形成一個隊列來依次處理,這樣用戶的調(diào)用方和處理方實現(xiàn)了完全解耦
- 節(jié)省CPU的時間和效率
RunLoop底層原理
我們還是查看源碼來進行探究
RunLoop代碼層級
-
NSRunloop:最上層的NSRunloop層實際上是對C語言實現(xiàn)的CFRunloop的一個封裝,實際上它沒干什么事,比如CFRunloop有一個過期時間是double類型,NSRunloop把它變味了NSDate類型; -
CFRunloop:這是真正干事的一層,源代碼是開源的,并且是跨平臺的; -
系統(tǒng)層:底層實現(xiàn)用到了GCD,mach kernel是蘋果的內(nèi)核,比如runloop的睡眠和喚醒就是用mach kernel來實現(xiàn)的。

RunLoop入口
在OC代碼中,Runloop是由系統(tǒng)默認開啟的,就再main函數(shù)中,會開啟主線程和RunLoop。如果沒有Runloop,那么main函數(shù)執(zhí)行完畢后,程序就退出了,這說明在UIApplicationMain函數(shù)中,開啟了一個和主線程相關的RunLoop,導致UIApplicationMain不會返回,一直在運行中,也就保證了程序的持續(xù)運行。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
復制代碼
接著我們查看源碼,發(fā)現(xiàn)CFRunLoopRun的底層實現(xiàn)結構也非常簡單,就是一個do...while循環(huán),我們可以把RunLoop看成一個死循環(huán)。如果沒有RunLoop,UIApplicationMain函數(shù)執(zhí)行完畢之后將直接返回,也就沒有程序持續(xù)運行一說了。
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
復制代碼
RunLoop與線程的關系
首先,iOS 開發(fā)中能遇到兩個線程對象: pthread_t 和 NSThread。過去蘋果有份文檔標明了 NSThread只是 pthread_t 的封裝,但那份文檔已經(jīng)失效了,現(xiàn)在它們也有可能都是直接包裝自最底層的 mach thread。蘋果并沒有提供這兩個對象相互轉(zhuǎn)換的接口,但不管怎么樣,可以肯定的是 pthread_t 和 NSThread 是一一對應的。比如,你可以通過 pthread_main_thread_np() 或 [NSThread mainThread] 來獲取主線程;也可以通過 pthread_self() 或 [NSThread currentThread] 來獲取當前線程。CFRunLoop 是基于 pthread 來管理的。
蘋果不允許直接創(chuàng)建 RunLoop,它只提供了兩個自動獲取的函數(shù):CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 這兩個函數(shù)內(nèi)部的邏輯大概是下面這樣:
?// 獲得當前線程的RunLoop對象,內(nèi)部調(diào)用_CFRunLoopGet0函數(shù)
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
?// 查看_CFRunLoopGet0方法
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
?// 如果為空則t設置為主線程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
?// 如果不存在runloop,則創(chuàng)建
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
?// 根據(jù)傳入的主線程獲取主線程對應的RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
?// 保存主線程 將主線程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
?// 從字典里面拿,將線程作為key從字典里獲取一個loop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
?// 如果loop為空,則創(chuàng)建一個新的loop,所以runloop會在第一次獲取的時候創(chuàng)建
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
?// 創(chuàng)建好之后,以線程為key runloop為value,一對一存儲在字典中,下次獲取的時候,則直接返回字典內(nèi)的runloop
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// do not release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
?//線程結束是銷毀loop
CFRelease(newLoop);
}
?// 如果傳入線程和當前線程相同
if (pthread_equal(t, pthread_self())) {
?// 注冊一個回調(diào),當線程銷毀時,順便也銷毀對應的RunLoop
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
復制代碼
通過源碼分析可以看出,線程和RunLoop之間是一一對應的,其關系是保存在一個Dictionary字典里。所以我們創(chuàng)建子線程RunLoop時,只需在子線程中獲取當前線程的RunLoop對象即可[NSRunLoop currentRunLoop];。如果不獲取,那子線程就不會創(chuàng)建與之相關聯(lián)的RunLoop,并且只能在一個線程的內(nèi)部獲取其RunLoop。
當通過調(diào)用[NSRunLoop currentRunLoop];方法獲取RunLoop時,會先看一下字典里有沒有子線程對應的RunLoop,如果有則直接返回RunLoop,如果沒有則會創(chuàng)建一個,并將與之對應的子線程存入字典中。當線程結束時,RunLoop會被銷毀。
總結一下Runloop與線程的關系
- 每條線程都有唯一的一個與之對應的RunLoop對象
- RunLoop保存在一個全局的Dictionary里,線程作為key,RunLoop作為value
- 主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要主動創(chuàng)建
- RunLoop在第一次獲取時創(chuàng)建,在線程結束時銷毀
RunLoop底層結構
CFRunLoopRef
通過源碼我們找到__CFRunLoop結構體
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop
{
// CoreFoundation 中的 runtime 基礎信息
CFRuntimeBase _base;
// 針對獲取 mode 列表操作的鎖
pthread_mutex_t _lock; /* locked for accessing mode list */
// 喚醒端口
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
// 是否使用過
Boolean _unused;
// runloop 運行會重置的一個數(shù)據(jù)結構
volatile _per_run_data *_perRunData; // reset for runs of the run loop
// runloop 所對應線程
pthread_t _pthread;
uint32_t _winthread;
// 存放 common mode 的集合
CFMutableSetRef _commonModes;
// 存放 common mode item 的集合
CFMutableSetRef _commonModeItems;
// runloop 當前所在 mode
CFRunLoopModeRef _currentMode;
// 存放 mode 的集合
CFMutableSetRef _modes;
// runloop 內(nèi)部 block 鏈表表頭指針
struct _block_item *_blocks_head;
// runloop 內(nèi)部 block 鏈表表尾指針
struct _block_item *_blocks_tail;
// 運行時間點
CFAbsoluteTime _runTime;
// 休眠時間點
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
// 每次 RunLoop 運行后會重置
typedef struct _per_run_data
{
uint32_t a;
uint32_t b;
uint32_t stopped; // runloop 是否停止
uint32_t ignoreWakeUps; // runloop 是否已喚醒
} _per_run_data;
// 鏈表節(jié)點
struct _block_item
{
// 指向下一個 _block_item
struct _block_item *_next;
// 要么是 string 類型,要么是集合類型,也就是說一個 block 可能對應單個或多個 mode
CFTypeRef _mode; // CFString or CFSet
// 存放的真正要執(zhí)行的 block
void (^_block)(void);
};
};
復制代碼
通過查看RunLoop的底層結構,我們發(fā)現(xiàn)了RunLoop也是一個結構體對象,其中有幾個主要的變量:
-
CFRunLoopModeRef _currentMode:runloop 當前所在 mode -
CFMutableSetRef _modes:存放 mode 的集合
通過上述變量,我們可以知道:
- RunLoop可以有多個mode對象
- Runloop在同一時間只能且必須在某一種特定的Mode下面Run,更換Mode時,必須要停止當前的Loop,然后重啟新的Loop,重啟的意思是退出當前的while循環(huán),然后重新設置一個新的while
CFRunLoopModeRef
CFRunLoopModeRef 其實是指向__CFRunLoopMode結構體的指針,__CFRunLoopMode結構體源碼如下
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode
{
// CoreFoundation 中的 runtime 基礎信息
CFRuntimeBase _base;
// 互斥鎖,加鎖前需要 runloop 先加鎖
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
// mode 的名稱
CFStringRef _name;
// mode 是否停止
Boolean _stopped;
char _padding[3];
// source0
CFMutableSetRef _sources0;
// source1
CFMutableSetRef _sources1;
// observers
CFMutableArrayRef _observers;
// timers
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
// port 的集合
__CFPortSet _portSet;
// observer 的 mask
CFIndex _observerMask;
// 如果定義了 GCD 定時器
#if USE_DISPATCH_SOURCE_FOR_TIMERS
// GCD 定時器
dispatch_source_t _timerSource;
// 隊列
dispatch_queue_t _queue;
// 當 GCD 定時器觸發(fā)時設置為 true
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
// 如果使用 MK_TIMER
#if USE_MK_TIMER_TOO
// MK_TIMER 的 port
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
// 定時器軟臨界點
uint64_t _timerSoftDeadline; /* TSR */
// 定時器硬臨界點
uint64_t _timerHardDeadline; /* TSR */
};
復制代碼
我們發(fā)現(xiàn),一個CFRunLoopModeRef也包含很多變量,主要有_sources0,_sources0兩個集合和_observers,_timers兩個數(shù)組。
這說明一個mode可以包含多種items模式
CFRunLoopSourceRef
CFRunLoopSourceRef是事件源(輸入源)。通過源碼可以發(fā)現(xiàn),其分為source0和source1兩個。
-
source0:處理App內(nèi)部事件,App自己負責管理(觸發(fā)),如UIEvent,CFSocket等; -
source1:由Runloop和內(nèi)核管理,mach port驅(qū)動,如CFMachPort(輕量級的進程間通信的方式,NSPort就是對它的封裝,還有Runloop的睡眠和喚醒就是通過它來做的),CFMessagePort;
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
struct __CFRunLoopSource
{
// CoreFoundation 中的 runtime 基礎信息
CFRuntimeBase _base;
uint32_t _bits;
// 互斥鎖
pthread_mutex_t _lock;
// source 的優(yōu)先級,值為小,優(yōu)先級越高
CFIndex _order; /* immutable */
// runloop 集合
CFMutableBagRef _runLoops;
// 一個聯(lián)合體,說明 source 要么為 source0,要么為 source1
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
typedef struct {
CFIndex version;
// source 的信息
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
// 判斷 source 相等的函數(shù)
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);
// source 要執(zhí)行的任務塊
void (*perform)(void *info);
} CFRunLoopSourceContext;
復制代碼
CFRunLoopObserverRef
CFRunLoopObserverRef是觀察者,每個Observer都包含了一個回調(diào)(函數(shù)指針),當RunLoop的狀態(tài)發(fā)生變化時,觀察者就能通過回調(diào)接受到這個變化。主要是用來向外界報告Runloop當前的狀態(tài)的更改。
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),// 即將進入Loop
kCFRunLoopBeforeTimers = (1UL << 1),// 即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2),// 即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5),// 即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7),// 即將退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU // 所有事件
};
復制代碼
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver
{
// CoreFoundation 中的 runtime 基礎信息
CFRuntimeBase _base;
// 互斥鎖
pthread_mutex_t _lock;
// observer 對應的 runloop
CFRunLoopRef _runLoop;
// observer 觀察了多少個 runloop
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
// observer 優(yōu)先級
CFIndex _order; /* immutable */
// observer 回調(diào)函數(shù)
CFRunLoopObserverCallBack _callout; /* immutable */
// observer 上下文
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFRunLoopObserverContext;
typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
復制代碼
CFRunLoopTimerRef
CFRunLoopTimerRef 是基于時間的觸發(fā)器,它和NSTimer是toll-free bridged的,可以混用。其包含一個時間長度和一個回調(diào)(函數(shù)指針)。
當其加入到RunLoop時,RunLoop會注冊對應的時間點,當時間點到時,RunLoop會被喚醒以執(zhí)行那個回調(diào)。
總結一下關于RunLoop的結構
-
RunLoop本質(zhì)也是一個結構體對象 -
RunloopMode是指的一個事件循環(huán)必須在某種模式下跑,系統(tǒng)會預定義幾個模式。一個Runloop有多個Mode; -
CFRunloopSource,CFRunloopTimer,CFRunloopObserver這些元素是在Mode里面的,Mode與這些元素的對應關系也是1對多的。但是必須至少有一個Source或者Timer,因為如果Mode為空,RunLoop運行到空模式不會進行空轉(zhuǎn),就會立刻退出。 -
CFRunloopSource分為source0(處理用戶事件)和source1(處理內(nèi)核事件) -
CFRunloopObserver是監(jiān)聽和通知Runloop狀態(tài)

RunLoop的Mode
RunLoop 有五種運行模式,其中常見的有1.2兩種。
1\. kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
2\. UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
3\. UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用,會切換到kCFRunLoopDefaultMode
4\. GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
5\. kCFRunLoopCommonModes: 這是一個占位用的Mode,作為標記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一種真正的Mode
復制代碼
Mode間的切換
我們平時在開發(fā)中一定遇到過,當我們使用NSTimer每一段時間執(zhí)行一些事情時滑動UIScrollView,NSTimer就會暫停,當我們停止滑動以后,NSTimer又會重新恢復的情況,這是由于RunloopMode必須在同一個模式下跑。
主線程的 RunLoop 里有兩個預置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。這兩個 Mode 都已經(jīng)被標記為”Common”屬性。DefaultMode 是 App 平時所處的狀態(tài),TrackingRunLoopMode是追蹤 ScrollView 滑動時的狀態(tài)。當你創(chuàng)建一個 Timer 并加到 DefaultMode 時,Timer 會得到重復回調(diào),但此時滑動一個TableView時,RunLoop 會將 mode 切換為 TrackingRunLoopMode,這時 Timer 就不會被回調(diào),并且也不會影響到滑動操作。
有時你需要一個 Timer,在兩個 Mode 中都能得到回調(diào),一種辦法就是將這個 Timer 分別加入這兩個 Mode。還有一種方式,就是將 Timer 加入到頂層的 RunLoop 的 “commonModeItems” 中?!眂ommonModeItems” 被 RunLoop 自動更新到所有具有”Common”屬性的 Mode 里去。
一個 Mode 可以將自己標記為”Common”屬性(通過將其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每當 RunLoop 的內(nèi)容發(fā)生變化時,RunLoop 都會自動將 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 標記的所有Mode里。
RunLoop啟動邏輯
我們知道在main函數(shù)啟動時,會有Runloop的用DefaultMode默認啟動和使用指定Mode進行啟動,相關的源碼如下,可以發(fā)現(xiàn),其核心邏輯都是調(diào)用了CFRunLoopRunSpecific函數(shù)
/// 用DefaultMode啟動
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
/// 用指定的Mode啟動,并允許設置RunLoop的超時時間
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
復制代碼
CFRunLoopRunSpecific
接著我們查看CFRunLoopRunSpecific函數(shù),根據(jù)其代碼,主要總結為以下幾個步驟:
- 從 runloop 中查找給定的 mode
- 將查找到的 mode 賦值到 runloop 的 _curentMode,也就是說在這 runloop 完成了 mode 的切換
- 調(diào)用核心函數(shù) __CFRunLoopRun
- 如果注冊了 observer,則通知runloop的開啟,運行,結束等狀態(tài)
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
{ /* DOES CALLOUT */
CHECK_FOR_FORK();
// 如果 runloop 正在回收中,直接返回 kCFRunLoopRunFinished ,表示 runloop 已經(jīng)完成
if (__CFRunLoopIsDeallocating(rl))
return kCFRunLoopRunFinished;
// 對 runloop 加鎖
__CFRunLoopLock(rl);
?// 從 runloop 中查找給定的 mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
?// 如果找不到 mode,且當前 runloop 的 currentMode 也為空,進入 if 邏輯
// __CFRunLoopModeIsEmpty 函數(shù)結果為空的話,說明 runloop 已經(jīng)處理完所有任務
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode))
{
Boolean did = false;
// 如果 currentMode 不為空
if (currentMode)
// 對 currentMode 解鎖
__CFRunLoopModeUnlock(currentMode);
// 對 runloop 解鎖
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
// 暫時取出 runloop 的 per_run_data
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
?// 取出 runloop 的當前 mode
CFRunLoopModeRef previousMode = rl->_currentMode;
?// 將查找到的 mode 賦值到 runloop 的 _curentMode,也就是說在這 runloop 完成了 mode 的切換
rl->_currentMode = currentMode;
?// 初始化返回結果 result
int32_t result = kCFRunLoopRunFinished;
?// 如果注冊了 observer 監(jiān)聽 kCFRunLoopEntry 狀態(tài)(即將進入 loop),則通知 observer
if (currentMode->_observerMask & kCFRunLoopEntry)
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
????// runloop 核心函數(shù) __CFRunLoopRun
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
?// 如果注冊了 observer 監(jiān)聽 kCFRunLoopExit 狀態(tài)(即將推出 loop),則通知 observer
if (currentMode->_observerMask & kCFRunLoopExit)
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
// 對 currentMode 解鎖
__CFRunLoopModeUnlock(currentMode);
// 還原原來的 previousPerRun
__CFRunLoopPopPerRunData(rl, previousPerRun);
// 還原原來的 mode
rl->_currentMode = previousMode;
// 對 runloop 解鎖
__CFRunLoopUnlock(rl);
return result;
}
復制代碼
CFRunLoopRun
CFRunLoopRun是RunLoop的核心函數(shù),一次運行循環(huán)就是一次 CFRunLoopRun 的運行。其5個參數(shù)分別代表的意義如下:
-
CFRunLoopRef rl: CFRunLoopRef 對象 -
CFRunLoopModeRef rlm: mode 的名稱 -
CFTimeInterval seconds: 超時時間 -
Boolean stopAfterHandle: 處理完 source 后是否直接返回 -
CFRunLoopModeRef previousMode: 前一次運行循環(huán)的 mode
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)
復制代碼
由于CFRunLoopRun的函數(shù)過長,邏輯比較復雜,所以我們精簡了代碼,只講解其中的一些的核心邏輯,主要有以下幾個步驟:
- 使用
dispatch_source_t創(chuàng)建一個定時器來處理超時相關的邏輯,如果沒設置會默認一個特別大的數(shù)字 - 啟動
do...while循環(huán)開始處理事件 - 通知
Observers: RunLoop 即將觸發(fā)Timer回調(diào)。 - 通知
Observers: RunLoop 即將觸發(fā)Source0(非port) 回調(diào)。 - 執(zhí)行被加入的block
- 如果有
Source1(基于port) 處于 ready 狀態(tài),直接處理這個 Source1 然后跳轉(zhuǎn)去處理消息。 - 通知
Observers: RunLoop 的線程即將進入休眠(sleep)。 - 調(diào)用
mach_msg等待接受mach_port的消息。線程將進入休眠, 直到被下面某一個事件喚醒。- 基于 port 的 Source 事件;
- Timer 時間到;
- RunLoop 自身的超時時間到了
- 被其他什么調(diào)用者手動喚醒
- 通知 Observers: RunLoop 的線程剛剛被喚醒了。
- 收到消息,處理消息。
* 如果一個 Timer 到時間了,觸發(fā)這個Timer的回調(diào)。
* 如果有dispatch到main_queue的block,執(zhí)行block。
* 如果一個 Source1 (基于port) 發(fā)出事件了,處理這個事件
- 執(zhí)行加入到Loop的block
- 根據(jù)當前 RunLoop 的狀態(tài)來判斷是否需要走下一個 loop。當被外部強制停止或 loop 超時時,就不繼續(xù)下一個 loop 了,否則繼續(xù)走下一個 loop 。
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)
{
// 聲明一個空的 GCD 定時器
dispatch_source_t timeout_timer = NULL;
// 初始化一個 「超時上下文」 結構體指針對象
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
...
int32_t retVal = 0;
do
{
// 通知 Observers 即將處理 Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知 Observers 即將處理 Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 處理 Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 處理 Source0
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle))
{
// 處理 Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 判斷有無 Source1
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL))
{
// 如果有 Source1,就跳轉(zhuǎn)到 handle_msg
goto handle_msg;
}
didDispatchPortLastTime = false;
// 通知 Observers 即將休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
do
{
if (kCFUseCollectableAllocator)
{
// objc_clear_stack(0);
// <rdar://problem/16393959>
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, &voucherState, &voucherCopy);
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)
{
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
}
else
{
if (msg && msg != (mach_msg_header_t *)msg_buffer)
free(msg);
}
}
else
{
// Go ahead and leave the inner loop.
break;
}
} while (1);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
// 通知 Observers 結束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:
if (被 timer 喚醒)
{
// 處理 timers
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
}
else if (被 GCD 喚醒)
{
// 處理 GCD 主隊列
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
else
{
// 被 Source1 喚醒
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
// 處理 Blocks
__CFRunLoopDoBlocks(rl, rlm);
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;
}
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
} while (0 == retVal);
return retVal;
}
復制代碼

RunLoop的退出
- 主線程銷毀RunLoop退出
- Mode中有一些Timer 、Source、 Observer,這些保證Mode不為空時保證RunLoop沒有空轉(zhuǎn)并且是在運行的,當Mode中為空的時候,RunLoop會立刻退出
- 我們在啟動RunLoop的時候可以設置什么時候停止
[NSRunLoop currentRunLoop]runUntilDate:<#(nonnull NSDate *)#>
[NSRunLoop currentRunLoop]runMode:<#(nonnull NSString *)#> beforeDate:<#(nonnull NSDate *)#>
復制代碼
RunLoop的應用
RunLoop在系統(tǒng)中的應用
AutoreleasePool
App啟動后,蘋果在主線程 RunLoop 里注冊了兩個 Observer,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一個 Observer 監(jiān)視的事件是Entry(即將進入Loop),其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池。其 order 是-2147483647,優(yōu)先級最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。
第二個 Observer 監(jiān)視了兩個事件: BeforeWaiting(準備進入休眠) 時調(diào)用_objc_autoreleasePoolPop() 和_objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop) 時調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放池。這個 Observer 的 order 是 2147483647,優(yōu)先級最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。
在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)、Timer回調(diào)內(nèi)的。這些回調(diào)會被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著,所以不會出現(xiàn)內(nèi)存泄漏,開發(fā)者也不必顯示創(chuàng)建 Pool 了。
事件響應
蘋果注冊了一個 Source1 (基于 mach port 的) 用來接收系統(tǒng)事件,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()。
當一個硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后,首先由 IOKit.framework 生成一個 IOHIDEvent 事件并由SpringBoard 接收。這個過程的詳細情況可以參考這里。SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸,加速,接近傳感器等幾種 Event,隨后用 mach port 轉(zhuǎn)發(fā)給需要的App進程。隨后蘋果注冊的那個 Source1 就會觸發(fā)回調(diào),并調(diào)用 _UIApplicationHandleEventQueue() 進行應用內(nèi)部的分發(fā)。
_UIApplicationHandleEventQueue() 會把IOHIDEvent 處理并包裝成 UIEvent 進行處理或分發(fā),其中包括識別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等。通常事件比如 UIButton 點擊、touchesBegin/Move/End/Cancel 事件都是在這個回調(diào)中完成的。
手勢識別
當上面的 _UIApplicationHandleEventQueue() 識別了一個手勢時,其首先會調(diào)用 Cancel 將當前的 touchesBegin/Move/End 系列回調(diào)打斷。隨后系統(tǒng)將對應的 UIGestureRecognizer 標記為待處理。
蘋果注冊了一個 Observer 監(jiān)測 BeforeWaiting (Loop即將進入休眠) 事件,這個Observer的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver(),其內(nèi)部會獲取所有剛被標記為待處理的 GestureRecognizer,并執(zhí)行GestureRecognizer的回調(diào)。
當有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時,這個回調(diào)都會進行相應處理。
界面更新
當在操作 UI 時,比如改變了 Frame、更新了 UIView/CALayer 的層次時,或者手動調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,這個 UIView/CALayer 就被標記為待處理,并被提交到一個全局的容器去。
蘋果注冊了一個 Observer 監(jiān)聽 BeforeWaiting(即將進入休眠) 和 Exit (即將退出Loop) 事件,回調(diào)去執(zhí)行一個很長的函數(shù): _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。這個函數(shù)里會遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實際的繪制和調(diào)整,并更新 UI 界面。
這個函數(shù)內(nèi)部的調(diào)用棧大概是這樣的:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
QuartzCore:CA::Transaction::observer_callback:
CA::Transaction::commit();
CA::Context::commit_transaction();
CA::Layer::layout_and_display_if_needed();
CA::Layer::layout_if_needed();
[CALayer layoutSublayers];
[UIView layoutSubviews];
CA::Layer::display_if_needed();
[CALayer display];
[UIView drawRect];
復制代碼
定時器
NSTimer 其實就是 CFRunLoopTimerRef,他們之間是 toll-free bridged 的。一個 NSTimer 注冊到 RunLoop 后,RunLoop 會為其重復的時間點注冊好事件。例如 10:00, 10:10, 10:20 這幾個時間點。RunLoop為了節(jié)省資源,并不會在非常準確的時間點回調(diào)這個Timer。Timer 有個屬性叫做 Tolerance (寬容度),標示了當時間點到后,容許有多少最大誤差。
如果某個時間點被錯過了,例如執(zhí)行了一個很長的任務,則那個時間點的回調(diào)也會跳過去,不會延后執(zhí)行。就比如等公交,如果 10:10 時我忙著玩手機錯過了那個點的公交,那我只能等 10:20 這一趟了。
CADisplayLink 是一個和屏幕刷新率一致的定時器(但實際實現(xiàn)原理更復雜,和 NSTimer 并不一樣,其內(nèi)部實際是操作了一個 Source)。如果在兩次屏幕刷新之間執(zhí)行了一個長任務,那其中就會有一幀被跳過去(和 NSTimer 相似),造成界面卡頓的感覺。在快速滑動TableView時,即使一幀的卡頓也會讓用戶有所察覺。Facebook 開源的 AsyncDisplayLink 就是為了解決界面卡頓的問題,其內(nèi)部也用到了 RunLoop.
PerformSelecter
當調(diào)用 NSObject 的 performSelecter:afterDelay: 后,實際上其內(nèi)部會創(chuàng)建一個 Timer 并添加到當前線程的 RunLoop 中。所以如果當前線程沒有 RunLoop,則這個方法會失效。
當調(diào)用 performSelector:onThread: 時,實際上其會創(chuàng)建一個 Timer 加到對應的線程去,同樣的,如果對應線程沒有 RunLoop 該方法也會失效。
關于GCD
實際上 RunLoop 底層也會用到 GCD 的東西,NSTimer 是用了 XNU 內(nèi)核的 mk_timer,我也仔細調(diào)試了一下,發(fā)現(xiàn) NSTimer 確實是由 mk_timer 驅(qū)動,而非 GCD 驅(qū)動的)。但同時 GCD 提供的某些接口也用到了 RunLoop, 例如 dispatch_async()。
當調(diào)用 dispatch_async(dispatch_get_main_queue(), block) 時,libDispatch 會向主線程的 RunLoop 發(fā)送消息,RunLoop會被喚醒,并從消息中取得這個 block,并在回調(diào) __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() 里執(zhí)行這個 block。但這個邏輯僅限于 dispatch 到主線程,dispatch 到其他線程仍然是由 libDispatch 處理的。
關于網(wǎng)絡請求
iOS 中,關于網(wǎng)絡請求的接口自下至上有如下幾層:
CFSocket
CFNetwork ->ASIHttpRequest
NSURLConnection ->AFNetworking
NSURLSession ->AFNetworking2, Alamofire
復制代碼
- CFSocket 是最底層的接口,只負責 socket 通信。
- CFNetwork 是基于 CFSocket 等接口的上層封裝,ASIHttpRequest 工作于這一層。
- NSURLConnection 是基于 CFNetwork 的更高層的封裝,提供面向?qū)ο蟮慕涌?,AFNetworking 工作于這一層。
- NSURLSession 是 iOS7 中新增的接口,表面上是和 NSURLConnection 并列的,但底層仍然用到了 NSURLConnection 的部分功能 (比如 com.apple.NSURLConnectionLoader 線程),AFNetworking2 和 Alamofire 工作于這一層。
下面主要介紹下 NSURLConnection 的工作過程。
通常使用 NSURLConnection 時,你會傳入一個 Delegate,當調(diào)用了 [connection start] 后,這個 Delegate 就會不停收到事件回調(diào)。實際上,start 這個函數(shù)的內(nèi)部會會獲取 CurrentRunLoop,然后在其中的 DefaultMode 添加了4個 Source0 (即需要手動觸發(fā)的Source)。
CFMultiplexerSource 是負責各種 Delegate 回調(diào)的,
CFHTTPCookieStorage是處理各種 Cookie 的。
當開始網(wǎng)絡傳輸時,我們可以看到 NSURLConnection 創(chuàng)建了兩個新線程:com.apple.NSURLConnectionLoader 和 com.apple.CFSocket.private。其中 CFSocket 線程是處理底層 socket 連接的。NSURLConnectionLoader這個線程內(nèi)部會使用 RunLoop 來接收底層 socket 的事件,并通過之前添加的 Source0通知到上層的 Delegate。

RunLoop在實際開發(fā)中的應用
AFNetworking
AFURLConnectionOperation 這個類是基于 NSURLConnection 構建的,其希望能在后臺線程接收 Delegate 回調(diào)。為此 AFNetworking 單獨創(chuàng)建了一個線程,并在這個線程中啟動了一個 RunLoop:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
復制代碼
RunLoop 啟動前內(nèi)部必須要有至少一個Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先創(chuàng)建了一個新的 NSMachPort 添加進去了。通常情況下,調(diào)用者需要持有這個 NSMachPort (mach_port) 并在外部線程通過這個 port 發(fā)送消息到 loop 內(nèi);但此處添加 port 只是為了讓 RunLoop 不至于退出,并沒有用于實際的發(fā)送消息。
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
復制代碼
當需要這個后臺線程執(zhí)行任務時,AFNetworking 通過調(diào)用 [NSObject performSelector:onThread:..] 將這個任務扔到了后臺線程的 RunLoop 中。
TableView延遲加載圖片
將setImage放到NSDefaultRunLoopMode去做,也就是在滑動的時候并不會去調(diào)用這個方法,而是會等到滑動完畢切換到NSDefaultRunLoopMode下面才會調(diào)用。
UIImage *downLoadImage = ...;
[self.avatarImageView performSelector:@selector(setImage:)
withObject:downloadImage
afterDelay:0
inModes:@[NSDefaultRunLoopMode]];
復制代碼
Crash的兼容處理
- program received signal:SIGABRT SIGABRT一般是過度release或者發(fā)送unrecogized selector導致。
-
EXC_BAD_ACCESS是訪問已被釋放的內(nèi)存導致,野指針錯誤。 由 SIGABRT 引起的Crash 是系統(tǒng)發(fā)這個signal給App,程序收到這個signal后,就會把主線程的RunLoop殺死,程序就Crash了 該例只針對 SIGABRT引起的Crash有效。
檢測卡頓
當App發(fā)生主線程卡頓時,我們可以通過RunLoop來監(jiān)聽到相對應的堆棧信息,然后進行優(yōu)化處理。
- 要想監(jiān)聽 RunLoop,你就首先需要創(chuàng)建一個
CFRunLoopObserverContext觀察者
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&runLoopObserverCallBack,&context);
復制代碼
- 將創(chuàng)建好的觀察者 runLoopObserver 添加到主線程 RunLoop 的 common 模式下觀察。然后,創(chuàng)建一個持續(xù)的子線程專門用來監(jiān)控主線程的 RunLoop 狀態(tài)。
- 一旦發(fā)現(xiàn)進入睡眠前的 kCFRunLoopBeforeSources 狀態(tài),或者喚醒后的狀態(tài) kCFRunLoopAfterWaiting,在設置的時間閾值內(nèi)一直沒有變化,即可判定為卡頓。接下來,我們就可以 dump 出堆棧的信息,從而進一步分析出具體是哪個方法的執(zhí)行時間過長。
//創(chuàng)建子線程監(jiān)控
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//子線程開啟一個持續(xù)的 loop 用來進行監(jiān)控
while (YES) {
long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
if (semaphoreWait != 0) {
if (!runLoopObserver) {
timeoutCount = 0;
dispatchSemaphore = 0;
runLoopActivity = 0;
return;
}
//BeforeSources 和 AfterWaiting 這兩個狀態(tài)能夠檢測到是否卡頓
if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
//將堆棧信息上報服務器的代碼放到這里
} //end activity
}// end semaphore wait
timeoutCount = 0;
}// end while
});
復制代碼
總結
- RunLoop的作用和意義
- 保持程序持續(xù)運行,程序一啟動就會開一個主線程,并接受用戶輸入
- 處理App中的各種事件,并決定程序在何時處理應該處理哪些Event
- 節(jié)省CPU資源,提高程序性能,在需要休眠時休眠
- 調(diào)用解耦,使用戶調(diào)用的時間有隊列來處理,調(diào)用方和處理方完全解耦
- Runloop與線程的關系
- 每條線程都有唯一的一個與之對應的RunLoop對象
- RunLoop保存在一個全局的Dictionary里,線程作為key,RunLoop作為value
- 主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要主動創(chuàng)建
- RunLoop在第一次獲取時創(chuàng)建,在線程結束時銷毀
- RunLoop的本質(zhì)與結構
- RunLoop本質(zhì)也是一個結構體對象
- RunloopMode是指的一個事件循環(huán)必須在某種模式下跑,系統(tǒng)會預定義幾個模式。一個Runloop有多個Mode
- CFRunloopSource,CFRunloopTimer,CFRunloopObserver這些元素是在Mode里面的,Mode與這些元素的對應關系也是1對多的。但是必須至少有一個Source或者Timer,因為如果Mode為空,RunLoop運行到空模式不會進行空轉(zhuǎn),就會立刻退出
- CFRunloopSource分為source0(處理用戶事件)和source1(處理內(nèi)核事件)
- CFRunloopObserver是監(jiān)聽和通知Runloop狀態(tài)
- RunLoop啟動邏輯
- 從 runloop 中查找給定的 mode
- 將查找到的 mode 賦值到 runloop 的 _curentMode,也就是說在這 runloop 完成了 mode 的切換
- 核心函數(shù) __CFRunLoopRun
- 如果注冊了 observer,則通知runloop的開啟,運行,結束等狀態(tài)
- CFRunLoopRun邏輯
- 使用dispatch_source_t創(chuàng)建一個定時器來處理超時相關的邏輯,如果沒設置會默認一個特別大的數(shù)字
- 啟動do...while循環(huán)開始處理事件
- 通知 Observers: RunLoop 即將觸發(fā) Timer和Source0 回調(diào)。
- 執(zhí)行被加入的block
- 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個 Source1 然后跳轉(zhuǎn)去處理消息。
- 通知 Observers: RunLoop 的線程即將進入休眠(sleep)。
- 調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進入休眠, 直到被下面某一個事件喚醒。
- 通知 Observers: RunLoop 的線程剛剛被喚醒了。
- 收到消息,處理消息。(Timer,dispatch,Source1等),并執(zhí)行block回調(diào)
- 根據(jù)當前 RunLoop 的狀態(tài)來判斷是否需要走下一個 loop。當被外部強制停止或 loop 超時時,就不繼續(xù)下一個 loop 了,否則繼續(xù)走下一個 loop 。
- RunLoop的退出
- 主線程銷毀RunLoop退出
- Mode中有一些Timer 、Source、 Observer,這些保證Mode不為空時保證RunLoop沒有空轉(zhuǎn)并且是在運行的,當Mode中為空的時候,RunLoop會立刻退出
- 我們在啟動RunLoop的時候可以設置什么時候停止
以下文章可以做一個學習參考:
GCD面試要點
block面試要點
Runtime面試要點
RunLoop面試要點
內(nèi)存管理面試要點
MVC、MVVM面試要點
網(wǎng)絡性能優(yōu)化面試要點
網(wǎng)絡編程面試要點
KVC&KVO面試要點
數(shù)據(jù)存儲面試要點
混編技術面試要點
設計模式面試要點
UI面試要點