文章涉及的代碼 源碼分析:LFRunLoop 測試Demo:LFRunLoopDemo
RunLoop是iOS中的循環(huán)機制,是App能夠正常運行的絕對主導者。
可能有同學說平時開發(fā)中可能比較少用到runloop,所以并不是特別在意,但是在開發(fā)遇到瓶頸的時候,理解RunLoop會讓你的水平有明顯的提高(PS:萬一面試會用到??)。
網(wǎng)上其他的文章已經(jīng)說得比較清楚了,比如RunLoop其實是一個while循環(huán),來不斷檢查是否有需要執(zhí)行的事件,比如每個線程都對應一個pthread線程,若不設(shè)置runloop,線程在執(zhí)行完任務后會立刻退出。那這些都是為什么呢?
閑話不多說,我們直接看一下RunLoop源碼
首先我們看CFRunLoop.h文件中幾個定義
/*
source0 只包含了一個回調(diào)(函數(shù)指針),source0是需要手動觸發(fā)的
Source,它并不能主動觸發(fā)事件,必須要先把它標記為signal狀態(tài)。使用時,
你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標記為待處
理,也就是通過uint32_t _bits來實現(xiàn)的,只有_bits標記Signaled狀態(tài)才會被處
理。然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理
這個事件。
*/
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);//當source加入到model觸發(fā)的回調(diào)
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);//當source從runloop中移除時觸發(fā)的回調(diào)
void (*perform)(void *info);//當source事件被觸發(fā)時的回調(diào),使用CFRunLoopSourceSignal方式觸發(fā)。
} CFRunLoopSourceContext;
/*
source1 包含了一個 mach_port 和一個回調(diào)(函數(shù)指針),被用于通過內(nèi)核
和其他線程相互發(fā)送消息。這種 Source 能主動喚醒 RunLoop 的線程。簡單
來說就是更加偏向于底層。
*/
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;
source上下文,runLoop的source源包括兩個 一個 source0 ,一個source1。
source1 比source0多了一個port,source0只能手動喚起RunLopp。
source1可以系統(tǒng)直接喚起,更偏向于內(nèi)核。
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFRunLoopObserverContext;
runLoop對應的觀察者上下文,可以觀察runLoop的狀態(tài)。很多關(guān)于性能優(yōu)化的框架都會在這里做文章。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
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFRunLoopTimerContext;
時間源上下文,可以自定義定時器來喚醒RunLoop。
然后我們來看一下下面的發(fā)方法
CF_EXPORT Boolean CFRunLoopContainsSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
CF_EXPORT Boolean CFRunLoopContainsObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
CF_EXPORT Boolean CFRunLoopContainsTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
發(fā)現(xiàn)正好和上面的幾個結(jié)構(gòu)體對應。這幾個方法就是來給RunLoop添加源的。
然后我們再來看一下CFRunLoop 和 mode
//runloop 的組成
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; //保證線程安全的鎖_lock /* locked for accessing mode list */
__CFPort _wakeUpPort;//喚起runloop的端口 // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;//對應的線程
uint32_t _winthread;
CFMutableSetRef _commonModes;//存儲的是字符串,記錄所有標記為common的mode
CFMutableSetRef _commonModeItems;//存儲所有commonMode的item(source、timer、observer)
CFRunLoopModeRef _currentMode;//當前運行的mode
CFMutableSetRef _modes;//存儲的是CFRunLoopModeRef,
struct _block_item *_blocks_head;//記錄了添加到runloop中的block,它也可以像其他源一樣被runloop處理,通過CFRunLoopPerformBlock可向runloop中添加block任務。
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
RunLoopMode結(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;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;//字典 key是mach_port_t,value是CFRunLoopSourceRef
__CFPortSet _portSet; //保存所有需要監(jiān)聽的port,比如_wakeUpPort,_timerPort都保存在這個數(shù)組中
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 USE_MK_TIMER_TOO
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 */
};
通過注釋可以看出,一個RunLoop可以包含多個Mode,每個mode里面可以包含多個源。如果mode里面沒有源,runLoop會立刻退出。
然后我們再看兩個地方一個是NSRunLoop 里面的
- (void)addPort:(NSPort *)aPort forMode:(NSRunLoopMode)mode;
這個是給RunLoop添加一個端口。這個方法可以使得線程?;睿嘈糯蠹叶伎催^AF2.x里面的AFURLConnectionOperation.m中就是通過給RunlLoop
添加一個空的MachPort來使得線程?;钸@個是為什么呢?我們先記下,過會兒來說。
然后我們再來記一個東西
void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void))
用該方法可以給RunLoop添加block
下面是函數(shù)調(diào)用棧顯示的方法,我們來看這個

畫圈的最后是一個Block,其他的有timer observer source dispatch 這些都是能夠喚起RunLoop 的方式。
上面是一些準備性的東西。然后我們開始看一下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;
}
//獲取當前runloop TSD為線程私有數(shù)據(jù),相當于只有當前線程可用的全局數(shù)據(jù)
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
這兩個方法一個是獲取主線程的runLoop 一個是獲取當前線程的RunLoop。
可以看到都調(diào)用了_CFRunLoopGet0只不過參數(shù)傳的為主線程或者當前線程。
然后我們看_CFRunLoopGet0
//根據(jù)線程獲取runloop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
//如果線程為空 默認為主線程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
//runloop 和 線程對應表為空 則創(chuàng)建
__CFSpinUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//根據(jù)主線程創(chuàng)建runloop 并且放入dict
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
//原子操作將dict賦給__CFRunLoops
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFSpinLock(&loopsLock);
}
// 根據(jù)線程獲得對應的loop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
//如果t線程對應的loop不存在,創(chuàng)建新的runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
// 將runloop 放入全局__CFRunLoops
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
__CFSpinUnlock(&loopsLock);
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。runLoop和線程被存在一個全局的字典__CFRunLoops中,如果__CFRunLoops不存在,則創(chuàng)建__CFRunLoops。然后根據(jù)線程創(chuàng)建出RunLoop,并且把線程和對應的RunLoop放在__CFRunLoops中。最后一句可以看出來,線程銷毀,對應的RunLoop也會隨著銷毀。
然后就是最重要的RunLoop的運行過程。首先我們看入口函數(shù)
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
可以看到默認情況下是在kCFRunLoopDefaultMode模式下執(zhí)行,或者可以使用CFRunLoopRunInMode來指定模式運行。
然后我們看CFRunLoopRunSpecific函數(shù)
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 根據(jù)modeName 獲取模式
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
//如果mode里沒有source/timer/observer,直接返回。
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
//volatile 數(shù)據(jù)可見性 每次都從內(nèi)存直接取數(shù)據(jù),編譯器不優(yōu)化
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
//取上一次運行的mode
CFRunLoopModeRef previousMode = rl->_currentMode;
//本次mode賦值
rl->_currentMode = currentMode;
//初始化一個result為kCFRunLoopRunFinished
int32_t result = kCFRunLoopRunFinished;
/*
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
};
*/
//即將進入循環(huán)
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//即將退出循環(huán)
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
我們只看一個地方

一目了然,如果RunLoop的mode中沒有對應的輸入源則立刻退出。
然后是最長的也是最重要的一個函數(shù)
//runloop進行循環(huán)
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//獲取當前內(nèi)核時間
uint64_t startTSR = mach_absolute_time();
//如果當前runLoop或者runLoopMode為停止狀態(tài)的話直接返回
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
//判斷是否是第一次在主線程中啟動RunLoop,如果是且當前RunLoop為主線程的RunLoop,那么就給分發(fā)一個隊列調(diào)度端口
////如果在主線程 && runloop是主線程的runloop && 該mode是commonMode,則給mach端口賦值為主線程收發(fā)消息的端口
mach_port_name_t dispatchPort = MACH_PORT_NULL;//用來保存主隊列(mainQueue)的端口。
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
#if USE_DISPATCH_SOURCE_FOR_TIMERS
//在ios中 port 事件能勾起 該執(zhí)行
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
// 如果運行模式的隊列存在
if (rlm->_queue) {
//給當前mode分發(fā)隊列端口
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
// 如果分發(fā)不成功,提示原因
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
//gcd 管理的定時器 用于實現(xiàn)runloop 超時機制
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_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
// 當前任務隊列的超時定時器
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
// 保存對應的數(shù)據(jù)到上下文
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
// 設(shè)置定時器的上下文
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
// 根據(jù)seconds計算超時時間
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
// 設(shè)置source的超時時間
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
// 開始計時
dispatch_resume(timeout_timer);
} else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
// 標記已經(jīng)運行結(jié)束
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
//初始化一個存放內(nèi)核消息的緩沖池
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
HANDLE livePort = NULL;
Boolean windowsMessageReceived = false;
#endif
//記錄了所有當前mode中需要監(jiān)聽的port,作為調(diào)用監(jiān)聽消息函數(shù)的參數(shù)。
__CFPortSet waitSet = rlm->_portSet;
//設(shè)置RunLoop為可以被喚醒狀態(tài)
__CFRunLoopUnsetIgnoreWakeUps(rl);
//2.通知Observers:通知即將處理timer
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//通知Observers:通知即將處理Source0(非port)。
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//處理加入到runLoop中的block, 處理非延遲的主線程調(diào)用 由CFRunLoopPerformBlock加進來的block 可喚醒runloop
__CFRunLoopDoBlocks(rl, rlm);
//執(zhí)行source0中的源事件
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
//有事件處理返回true,沒有事件返回false
if (sourceHandledThisLoop) {
//處理加入到runLoop中的block
__CFRunLoopDoBlocks(rl, rlm);
}
//如果沒有Sources0事件處理 并且 沒有超時,poll為false
//如果有Sources0事件處理 或者 超時,poll都為true
//0ULL == timeout_context->termTSR 代表超時判斷
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//如果主線程runloop 端口存在、didDispatchPortLastTime為假(首次執(zhí)行不會進入判斷,因為didDispatchPortLastTime為true)
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
msg = (mach_msg_header_t *)msg_buffer;
//__CFRunLoopServiceMachPort用于接受指定端口(一個也可以是多個)的消息,最后一個參數(shù)代表當端口無消息的時候是否休眠,0是立刻返回不休眠,TIMEOUT_INFINITY代表休眠
//處理通過GCD派發(fā)到主線程的任務,這些任務優(yōu)先級最高會被最先處理
//如果有Source1,就直接跳轉(zhuǎn)去處理消息。(文檔說是檢測source1,不過源碼看來是檢測dispatchPort---gcd端口事件)
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
//如果端口有事件則跳轉(zhuǎn)至handle_msg
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
//之前沒有處理過source0或者沒有超時,也沒有source1消息,就讓線程進入睡眠。
//通知 Observers: RunLoop 的線程即將進入休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 標志當前runLoop為休眠狀態(tài)
__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
// 進入循環(huán)開始不斷的讀取端口信息,如果端口有喚醒信息則喚醒當前runLoop
do {
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
// 調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進入休眠, 直到被下面某一個事件喚醒。
/// ? 一個基于 port 的Source 的事件。
/// ? 一個 Timer 到時間了
/// ? RunLoop 自身的超時時間到了
/// ? 被其他什么調(diào)用者手動喚醒
//如果poll為no,且waitset中無port有消息,線程進入休眠;否則喚醒
// 如果沒有Sources0事件處理 并且 沒有超時,poll為false
// __CFRunLoopServiceMachPort用于接受指定端口(一個也可以是多個)的消息,最后一個參數(shù)代表當端口無消息的時候是否休眠,0是立刻返回不休眠,TIMEOUT_INFINITY代表休眠
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
//livePort是modeQueuePort,則代表為當前mode隊列的端口
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));
//知道Timer被激活了才跳出二級循環(huán)繼續(xù)循環(huán)一級循環(huán)
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);
}
}
//如果livePort不為modeQueuePort,runLoop被喚醒。這代表__CFRunLoopServiceMachPort給出的livePort只有兩種可能:一種情況為MACH_PORT_NULL,另一種為真正獲取的消息的端口。
else {
// Go ahead and leave the inner loop.
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
// Here, use the app-supplied 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, &livePort, &windowsMessageReceived);
#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);
// user callouts now OK again
//取消runloop的休眠狀態(tài)
__CFRunLoopUnsetSleeping(rl);
//通知 Observers:RunLoop的線程剛剛被喚醒了。
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//處理端口消息
handle_msg:;
//設(shè)置此時runLoop忽略端口喚醒(保證線程安全)
__CFRunLoopSetIgnoreWakeUps(rl);
#if DEPLOYMENT_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(rlm);
__CFRunLoopUnsetSleeping(rl);
// If we have a new live port then it will be handled below as normal
}
#endif
// 處理待處理的事件
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
}
//struct __CFRunLoop中有這么一項:__CFPort _wakeUpPort,用于手動將當前runloop線程喚醒,通過調(diào)用CFRunLoopWakeUp完成,CFRunLoopWakeUp會向_wakeUpPort發(fā)送一條消息
else if (livePort == rl->_wakeUpPort) {
//只有外界調(diào)用CFRunLoopWakeUp才會進入此分支,這是外部主動喚醒runLoop的接口
CFRUNLOOP_WAKEUP_FOR_WAKEUP();//返回2重新循環(huán)
// do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
// Always reset the wake up port, or risk spinning forever
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
//如果是定時器事件
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
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
//處理因timer的喚醒。
// 如果一個 Timer 到時間了,觸發(fā)這個Timer的回調(diào)。
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
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
//如果是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
//處理異步方法喚醒。處理gcd dispatch到main_queue的block,執(zhí)行block。
/*有判斷是否是在MainRunLoop,有獲取Main_Queue 的port,并且有調(diào)用 Main_Queue 上的回調(diào),這只能是是 GCD 主隊列上的異步任務。即:dispatch_async(dispatch_get_main_queue(), block)產(chǎn)生的任務。
*/
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
//處理Source1 (基于port)
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// Despite the name, this works for windows handles as well
//過濾macPort消息,有一些消息不一定是runloop中注冊的,這里只處理runloop中注冊的消息,在rlm->_portToV1SourceMap通過macPort找有沒有對應的runloopMode
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;
// 處理Source1
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
//當前線程處理完source1,給發(fā)消息的線程反饋消息
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#elif DEPLOYMENT_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);
//進入run loop時傳入的參數(shù),處理完事件就返回
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
}
// 超出傳入?yún)?shù)標記的超時時間了
else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
}
// 被外部調(diào)用者強制停止了
else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
}
// source/timer/observer一個都沒有了
else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
// 如果沒超時,mode里沒空,loop也沒被停止,那繼續(xù)loop
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
代碼很長,但是基本上都有很詳細的注釋。
RunLoop其實就是一個do while 循環(huán),當有需要處理事件時就處理,空閑時就進入休眠同時run有port事件注冊,子循環(huán)就在對應的port端口等待事件來喚醒。然后超時時間是GCD 來實現(xiàn)的。
然后關(guān)于RunLoop的應用,引用YY大神的內(nèi)容
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 了。
手勢識別
識別了一個手勢時,其首先會調(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 界面。
定時器
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 這一趟了。
PerformSelecter
當調(diào)用 NSObject 的 performSelecter:afterDelay: 來實現(xiàn)延遲執(zhí)行,實際上其內(nèi)部會創(chuàng)建一個 Timer 并添加到當前線程的 RunLoop 中。所以如果當前線程沒有 RunLoop,則這個方法會失效。
當調(diào)用 performSelector:onThread: 時,實際上其會創(chuàng)建一個 Timer 加到對應的線程去,同樣的,如果對應線程沒有 RunLoop 該方法也會失效。
GCD
在看runloop執(zhí)行過程的源碼中,可以知道RunLoop 的超時時間就是使用 GCD 中的 dispatch_source_t來實現(xiàn)的。
當調(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 處理的。
具體的框架除了AF2.x 使用的RunLoop來?;?,還有一個百度的UITableView+FDTemplateLayoutCell 這個早起也是利用了RunLoop 來優(yōu)化緩存行高的時機(ps:目前已經(jīng)廢棄)。還有一個是facebook的Texture,一個效率極高的渲染框架。有時間大家可以去看看源碼。