iOS Runloop
[TOC]
1. 什么是Runloop
1.1 Runloop簡(jiǎn)介
Runloop
即運(yùn)行循環(huán)。NSRunloop
是OC Foundation
框架中的一個(gè)非常重要的類。
Runloop
是事件接收和分發(fā)機(jī)制的一個(gè)實(shí)現(xiàn),是線程相關(guān)的基礎(chǔ)框架的一部分,一個(gè)Runloop就是一個(gè)事件處理的循環(huán)哮肚,用來不停的調(diào)度工作以及處理輸入事件泌辫。這也就是為什么APP
放在那里不去動(dòng)它怎棱,過一會(huì)在操作它還是會(huì)給你反饋
Runloop
本質(zhì)是一個(gè)do while
循環(huán),但是它可以休眠桦踊,有任務(wù)就執(zhí)行,沒任務(wù)就休眠终畅,但是它和普通while
循環(huán)還是有區(qū)別的籍胯,普通的while
循環(huán)會(huì)導(dǎo)致CPU進(jìn)入忙等待狀態(tài),即一直消耗CPU离福,而Runloop
不會(huì)杖狼,Runloop
是一種閑等待,即Runloop
具備休眠功能妖爷。
1.2 Runloop 的作用
- 保持程序的運(yùn)行蝶涩,接受用戶的輸入
- 決定程序在何時(shí)處理一些Event(觸摸、定時(shí)器絮识、performSelector)
- 調(diào)用解耦
(message queue)
- 節(jié)省CPU時(shí)間绿聘,沒任務(wù)的時(shí)候休眠,有任務(wù)的時(shí)候處理
1.3 依賴NSRunloop的框架
- NSTimer
- UIEvent
- autorelease
- NSObject(NSDelaydPerforming)
- NSObject(NSThreadPerformAddtion)
- CADisplayLink
- CATransition
- CAAnimation
- dispatch_get_main_queue
1.4 Runloop消息類型
消息類型這里我們就看一下那張經(jīng)典的圖
port
:
監(jiān)聽程序的Mach Ports
次舌,是一個(gè)比較底層的東西熄攘,可以簡(jiǎn)單理解為內(nèi)核通過port這種方式將信息發(fā)送,而mach則監(jiān)聽內(nèi)核發(fā)來的port信息彼念,然后將其整理挪圾,打包發(fā)給runloop-
Customer
:
很明顯浅萧,由開發(fā)人員自己發(fā)送。不僅僅是發(fā)送哲思,過程也會(huì)相當(dāng)復(fù)雜惯殊,蘋果也提供了一個(gè)CFRunLoopSource
來幫助處理。由于很少用到也殖,可以簡(jiǎn)單說下核心土思,但是對(duì)幫助我們理解runloop卻很有幫助:- 定義輸入源(數(shù)據(jù)結(jié)構(gòu))
- 將輸入源添加到runloop,那么這樣就有了接受者忆嗜,即為R1
- 協(xié)調(diào)輸入源的客戶端(單獨(dú)線程)己儒,專門監(jiān)聽消息,然后將消息打包成runloop能夠處理的樣式捆毫,即第一步定義的輸入源闪湾。它類似Mach的功能
- 誰來發(fā)送消息的問題?上面的machport是由內(nèi)核發(fā)送的绩卤。自定義的當(dāng)然要我們自己發(fā)送了途样,首先必須是另一個(gè)線程來發(fā)送(當(dāng)然如果只是測(cè)試的話可以和第三步在同一個(gè)線程),先發(fā)送消息給輸入源濒憋,然后后喚醒R1何暇,因R1一般處于休眠狀態(tài),然后R1根據(jù)輸入源來做相應(yīng)的處理凛驮。
Selector Sources
:它的事件發(fā)送是同步的裆站,這個(gè)用的比較多-
Observers
:觀察者,首先它并不屬于事件源(不會(huì)影響runloop的生命周期)黔夭,它比較特殊宏胯,用于觀察runloop自身的一些狀態(tài)的,有以下幾種:- 進(jìn)入runloop
- runloop即將執(zhí)行定時(shí)器
- runloop即將執(zhí)行輸入源(Port本姥,Customer肩袍,Selector Source)
- runloop即將休眠
- runloop 被喚醒,在處理完喚醒它的事件之前
- 退出
2. Runloop 探索
我們知道NSRunloop
是對(duì)CFRunloop
的封裝婚惫,我們可以下載CFRunloop
源碼氛赐,下載地址,中可以下載各個(gè)版本的源碼辰妙。
2.1 Runloop 與線程的關(guān)系
在日常開發(fā)中鹰祸,我們通常使用以下兩種方式獲取Runloop
// 主運(yùn)行循環(huán)
CFRunLoopRef mainRunloop = CFRunLoopGetMain();
// 當(dāng)前運(yùn)行循環(huán)
CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
查看一下源碼:
CFRunLoopGetMain:
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;
}
CFRunLoopGetCurrent:
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
// 調(diào)用_CFRunLoopGet0
return _CFRunLoopGet0(pthread_self());
}
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
// 如果t為nil,也就是沒有線程密浑,則標(biāo)記為主線程蛙婴,也就是默認(rèn)情況都通過主線程處理
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
// 創(chuàng)建全局字典,標(biāo)記為kCFAllocatorSystemDefault
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 創(chuàng)建主運(yùn)行循環(huán)
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 字典的key-value綁定尔破,這里就是主線程綁定主運(yùn)行循環(huán)
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 其他線程街图,也就是非主線程的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
// 如果沒有獲取到浇衬,就創(chuàng)建一個(gè)新的runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
// 將新建的runloop與線程進(jìn)行綁定,也是通過字典的key-value方式
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;
}
根據(jù)以上源碼餐济,我們可以知道Runloop
有兩種耘擂,一種是主線程的,另一種是其他線程的絮姆,Runloop
與線程在一個(gè)全局字典中是一一對(duì)應(yīng)的醉冤。
2.2 Runloop的創(chuàng)建
根據(jù)上一節(jié)中的代碼我們可以知道,創(chuàng)建Runloop
的方法是__CFRunLoopCreate
篙悯,下面我們就看看這個(gè)方法:
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
CFRunLoopRef loop = NULL;
CFRunLoopModeRef rlm;
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
// CFRunLoopRef 類型的 一個(gè) Instance
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;
}
2.2.1 CFRunLoopRef
通過以上代碼我們可以看到loop
是一個(gè)CFRunLoopRef
類型蚁阳,源碼中主要是一些賦值操作,下面我們就看看CFRunLoopRef
:
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // 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;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
CFRunLoopModeRef:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
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 */
};
從上面的結(jié)構(gòu)體定義中我們可以知道鸽照,一個(gè)Runloop
依賴于多個(gè)mode
螺捐,意味著一個(gè)Runloop
需要處理多個(gè)事物,然后一個(gè)mode
有對(duì)應(yīng)著多個(gè)items
矮燎,而一個(gè)items
中有包含了timer
定血、source
、observer
诞外。也就是下面這幅圖:
所以Runloop的結(jié)構(gòu)如下:
2.2.2 Mode的類型
其中mode
在蘋果官方文檔中體積的有五個(gè)澜沟,而在iOS中公開暴露出來的只有NSDefaultRunLoopMode
和NSRunLoopCommonModes
。NSRunLoopCommonModes
實(shí)際上是一個(gè)mode
的集合浅乔,默認(rèn)包含NSDefaultRunLoopMode
和NSEventTrackingRunLoopMode
倔喂。
-
NSDefaultRunLoopMode
:默認(rèn)的mode铝条,一般情況下都是在這個(gè)mode -
NSConnectionReplyMode
:處理NSConnection對(duì)象相關(guān)事件靖苇,系統(tǒng)內(nèi)部使用,用戶基本不會(huì)使用班缰。 -
NSModalPanelRunLoopMode
:處理modal panels事件贤壁。 -
NSEventTrackingRunLoopMode
:使用這個(gè)Mode去跟著來自用戶交互的事件,比如scrollView的滾動(dòng) -
NSRunLoopCommonModes
:偽模式埠忘,靈活性更好
2.2.3 Source & Timer & Observer
-
Source
表示可以喚醒Runloop
的一些事件脾拆,例如用戶點(diǎn)擊了屏幕,就會(huì)創(chuàng)建一個(gè)Runloop莹妒,主要分為Source0
和Source1
-
Source0
表示非系統(tǒng)事件名船,即用戶自定義的事件 -
Source1
表示系統(tǒng)事件,主要負(fù)責(zé)底層的通訊旨怠,具備喚醒能力
-
-
Timer
就是常用NSTimer
定時(shí)器這一類 -
Observer
主要用于監(jiān)聽Runloop的狀態(tài)變化渠驼,并作出一定響應(yīng),主要有以下一些狀態(tài)
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
// 進(jìn)入Runloop
kCFRunLoopEntry = (1UL << 0),
// 即將處理timers
kCFRunLoopBeforeTimers = (1UL << 1),
// 即將處理source
kCFRunLoopBeforeSources = (1UL << 2),
// 即將進(jìn)入休眠
kCFRunLoopBeforeWaiting = (1UL << 5),
// 被喚醒
kCFRunLoopAfterWaiting = (1UL << 6),
// 退出Runloop
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
2.2.4 驗(yàn)證Runloop 與 Mode 是一對(duì)多
下面我們通過代碼來驗(yàn)證一下Runloop與Mode的關(guān)系鉴腻。
- 通過
lldb
命令獲取mainRunloop
和currentRunloop
的currentMode
po CFRunLoopCopyCurrentMode(mainRunloop)
po CFRunLoopCopyCurrentMode(currentRunloop)
從這里可以看到迷扇,runloop在運(yùn)行時(shí)的mode只有一個(gè)百揭。
我們經(jīng)常遇到的一個(gè)經(jīng)典問題,當(dāng)刷新UI的時(shí)候有時(shí)計(jì)時(shí)器是不工作的蜓席,所以就是當(dāng)前mode
是NSEventTrackingRunLoopMode
器一,而不是timer
一開始指定的NSDefaultRunLoopMode
。所以我們不改變定時(shí)器的運(yùn)行模式厨内,讓他在這個(gè)模式下運(yùn)行timer
就不響應(yīng)了祈秕,解決方法就是將定時(shí)器加入到UITrackingRunLoopMode
和NSRunLoopCommonModes
。另外雏胃,還有一種方法就是:將timer
加入到頂層的Runloop的commonModeItems
中踢步。commonModeItems
被Runloop自動(dòng)更新到所有具有common
屬性的Mode
里去。
下面我們?cè)讷@取一下mainRunloop
的所有模型丑掺,使用po CFRunLoopCopyAllModes(mainRunloop)
可以看到這里是一個(gè)數(shù)組获印,也就說說runloop
和CFRunloopMode
具有一對(duì)多的關(guān)系。
2.2.5 驗(yàn)證mode 與 item 是一對(duì)多
下面我們?cè)賮眚?yàn)證一下mode和item的關(guān)系街州,添加如下代碼兼丰,添加斷點(diǎn),通過bt
命令在lldb
中打印堆棧信息
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"99999");
}];
}
點(diǎn)擊屏幕唆缴,通過bt
命令查看堆棧信息:
在Runloop源碼中可以查看Item
類型鳍征,有以下幾種:
- GCD主隊(duì)列:
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
- observer:
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
- timer:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
- block:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
- source0:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
- source1:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
下面我們通過一個(gè)例子來說明一下,一般初始化timer時(shí)面徽,都會(huì)將timer通過addTimer:forMode:
方法添加到runloop中艳丛,于是在源碼中查找addTimer
的相關(guān)方法,即CFRunLoopAddTimer
方法趟紊,其源碼實(shí)現(xiàn)如下:
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
__CFRunLoopLock(rl);
// 判斷是否是 kCFRunLoopCommonModes 類型
if (modeName == kCFRunLoopCommonModes) {
// 是 kCFRunLoopCommonModes 類型
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
// runloop 與 mode 是一對(duì)多的氮双,mode與item也是一對(duì)多的
CFSetAddValue(rl->_commonModeItems, rlt);
if (NULL != set) {
CFTypeRef context[2] = {rl, rlt};
/* add new item to all common-modes */
// 執(zhí)行
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
} else {
// 如果不是kCFRunLoopCommonModes類型,則查找runloop的類型
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
if (NULL != rlm) {
if (NULL == rlm->_timers) {
CFArrayCallBacks cb = kCFTypeArrayCallBacks;
cb.equal = NULL;
rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
}
}
// 判斷mode是否匹配
if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
__CFRunLoopTimerLock(rlt);
if (NULL == rlt->_runLoop) {
rlt->_runLoop = rl;
} else if (rl != rlt->_runLoop) {
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
return;
}
// 如果匹配霎匈,則將runloop加進(jìn)去戴差,而runloop的執(zhí)行依賴于 [runloop run]
CFSetAddValue(rlt->_rlModes, rlm->_name);
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopTimerFireTSRLock();
__CFRepositionTimerInMode(rlm, rlt, false);
__CFRunLoopTimerFireTSRUnlock();
if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
// Normally we don't do this on behalf of clients, but for
// backwards compatibility due to the change in timer handling...
if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
}
}
if (NULL != rlm) {
__CFRunLoopModeUnlock(rlm);
}
}
__CFRunLoopUnlock(rl);
}
根據(jù)上面的源碼我們可以看到:
- 首先判斷是否是
kCFRunLoopCommonModes
類型 - 是的話就正常處理
- 不是的話就查找runloop的mode進(jìn)行匹配處理
- 其中
kCFRunLoopCommonModes
不是一種模式,是一種抽象的偽模式铛嘱,比defauteMode更加靈活 - 通過
CFSetAddValue(rl->_commonModeItems, rlt);
可以得知暖释,runloop與mode是一對(duì)多的關(guān)系,墨吓,同時(shí)也可以得出mode
與item
也是一對(duì)多的關(guān)系
2.3 Runloop的執(zhí)行
在日常開發(fā)中球匕,我們都知道Runloop
的執(zhí)行依賴于run
方法,從下面的堆棧信息中可以看出帖烘,其底層執(zhí)行的是__CFRunLoopRun
方法亮曹。
下面我們看看__CFRunLoopRun
的源碼(源碼很多,精簡(jiǎn)的,感興趣的可以下載看一下):
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
...
do{
...
//通知 Observers: 即將處理timer事件
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//通知 Observers: 即將處理Source事件
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//處理Blocks
__CFRunLoopDoBlocks(rl, rlm);
//處理sources0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
//處理sources0返回為YES
if (sourceHandledThisLoop) {
// 處理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
...
//如果是timer
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);
}
}
...
//如果是source1
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
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 DEPLOYMENT_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
}
...
}while (0 == retVal);
...
}
通過源碼我們可以看到乾忱,針對(duì)不同的事件讥珍,有不同的處理:
- 如有有
observer
,則調(diào)用__CFRunLoopDoObservers
- 如果有
block
窄瘟,則調(diào)用__CFRunLoopDoBlocks
- 如果有
timer
衷佃,則調(diào)用__CFRunLoopDoTimers
- 如果有
source0
,則調(diào)用__CFRunLoopDoSources0
- 如果有
source1
蹄葱,則調(diào)用__CFRunLoopDoSource1
- 這里我們以
timer
為例氏义,看看__CFRunLoopDoTimers
方法:
// rl and rlm are locked on entry and exit
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) { /* DOES CALLOUT */
Boolean timerHandled = false;
CFMutableArrayRef timers = NULL;
for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
if (rlt->_fireTSR <= limitTSR) {
if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(timers, rlt);
}
}
}
// 循環(huán)遍歷執(zhí)行每一個(gè)timer
for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
timerHandled = timerHandled || did;
}
if (timers) CFRelease(timers);
return timerHandled;
}
- 下面我們?cè)倏纯?code>__CFRunLoopDoTimer方法:
// mode and rl are locked on entry and exit
static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) { /* DOES CALLOUT */
Boolean timerHandled = false;
uint64_t oldFireTSR = 0;
/* Fire a timer */
CFRetain(rlt);
__CFRunLoopTimerLock(rlt);
if (__CFIsValid(rlt) && rlt->_fireTSR <= mach_absolute_time() && !__CFRunLoopTimerIsFiring(rlt) && rlt->_runLoop == rl) {
void *context_info = NULL;
void (*context_release)(const void *) = NULL;
if (rlt->_context.retain) {
context_info = (void *)rlt->_context.retain(rlt->_context.info);
context_release = rlt->_context.release;
} else {
context_info = rlt->_context.info;
}
Boolean doInvalidate = (0.0 == rlt->_interval);
__CFRunLoopTimerSetFiring(rlt);
// Just in case the next timer has exactly the same deadlines as this one, we reset these values so that the arm next timer code can correctly find the next timer in the list and arm the underlying timer.
rlm->_timerSoftDeadline = UINT64_MAX;
rlm->_timerHardDeadline = UINT64_MAX;
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopTimerFireTSRLock();
oldFireTSR = rlt->_fireTSR;
__CFRunLoopTimerFireTSRUnlock();
__CFArmNextTimerInMode(rlm, rl);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
// 與上面在調(diào)用堆棧中看到的方法一致
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
CHECK_FOR_FORK();
if (doInvalidate) {
CFRunLoopTimerInvalidate(rlt); /* DOES CALLOUT */
}
if (context_release) {
context_release(context_info);
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopTimerLock(rlt);
timerHandled = true;
__CFRunLoopTimerUnsetFiring(rlt);
}
if (__CFIsValid(rlt) && timerHandled) {
/* This is just a little bit tricky: we want to support calling
* CFRunLoopTimerSetNextFireDate() from within the callout and
* honor that new time here if it is a later date, otherwise
* it is completely ignored. */
if (oldFireTSR < rlt->_fireTSR) {
/* Next fire TSR was set, and set to a date after the previous
* fire date, so we honor it. */
__CFRunLoopTimerUnlock(rlt);
// The timer was adjusted and repositioned, during the
// callout, but if it was still the min timer, it was
// skipped because it was firing. Need to redo the
// min timer calculation in case rlt should now be that
// timer instead of whatever was chosen.
__CFArmNextTimerInMode(rlm, rl);
} else {
uint64_t nextFireTSR = 0LL;
uint64_t intervalTSR = 0LL;
if (rlt->_interval <= 0.0) {
} else if (TIMER_INTERVAL_LIMIT < rlt->_interval) {
intervalTSR = __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
} else {
intervalTSR = __CFTimeIntervalToTSR(rlt->_interval);
}
if (LLONG_MAX - intervalTSR <= oldFireTSR) {
nextFireTSR = LLONG_MAX;
} else {
if (intervalTSR == 0) {
// 15304159: Make sure we don't accidentally loop forever here
CRSetCrashLogMessage("A CFRunLoopTimer with an interval of 0 is set to repeat");
HALT;
}
uint64_t currentTSR = mach_absolute_time();
nextFireTSR = oldFireTSR;
while (nextFireTSR <= currentTSR) {
nextFireTSR += intervalTSR;
}
}
CFRunLoopRef rlt_rl = rlt->_runLoop;
if (rlt_rl) {
CFRetain(rlt_rl);
CFIndex cnt = CFSetGetCount(rlt->_rlModes);
STACK_BUFFER_DECL(CFTypeRef, modes, cnt);
CFSetGetValues(rlt->_rlModes, (const void **)modes);
// To avoid A->B, B->A lock ordering issues when coming up
// towards the run loop from a source, the timer has to be
// unlocked, which means we have to protect from object
// invalidation, although that's somewhat expensive.
for (CFIndex idx = 0; idx < cnt; idx++) {
CFRetain(modes[idx]);
}
__CFRunLoopTimerUnlock(rlt);
for (CFIndex idx = 0; idx < cnt; idx++) {
CFStringRef name = (CFStringRef)modes[idx];
modes[idx] = (CFTypeRef)__CFRunLoopFindMode(rlt_rl, name, false);
CFRelease(name);
}
__CFRunLoopTimerFireTSRLock();
rlt->_fireTSR = nextFireTSR;
rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
for (CFIndex idx = 0; idx < cnt; idx++) {
CFRunLoopModeRef rlm = (CFRunLoopModeRef)modes[idx];
if (rlm) {
__CFRepositionTimerInMode(rlm, rlt, true);
}
}
__CFRunLoopTimerFireTSRUnlock();
for (CFIndex idx = 0; idx < cnt; idx++) {
__CFRunLoopModeUnlock((CFRunLoopModeRef)modes[idx]);
}
CFRelease(rlt_rl);
} else {
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopTimerFireTSRLock();
rlt->_fireTSR = nextFireTSR;
rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
__CFRunLoopTimerFireTSRUnlock();
}
}
} else {
__CFRunLoopTimerUnlock(rlt);
}
CFRelease(rlt);
return timerHandled;
}
在上面的代碼中我們可以清楚的看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
的調(diào)用,這與我們?cè)谡{(diào)用堆棧中看到的是一致的图云。
所以關(guān)于timer
的調(diào)用過程是這樣的:
- 為自定義的timer設(shè)置
mode
惯悠,并將其加入到Runloop
中 - 在Runloop的
run
方法執(zhí)行時(shí),會(huì)調(diào)用__CFRunLoopDoTimers
方法收集完timer
后就會(huì)循環(huán)遍歷執(zhí)行所有timer - 循環(huán)遍歷的時(shí)候調(diào)用的是
__CFRunLoopDoTimer
方法 -
timer
執(zhí)行完畢后會(huì)執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)
以上就是timer
執(zhí)行的簡(jiǎn)單分析竣况,對(duì)于observer
克婶、block
、source0
丹泉、source1
等的指向也都大同小異情萤,感興趣的還可以去看看官方文檔。
3. Runloop 的原理
3.1 CFRunLoopRun
分析到這里我們重新回到最初的地方CFRunLoopRun
摹恨,這個(gè)可以通過堆棧信息中看出筋岛,也可以從上面的分析中總結(jié)出來。
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
這里是一個(gè)do while
循環(huán)晒哄,也就是我們一開始說的那個(gè)do while
循環(huán)
這里會(huì)調(diào)用CFRunLoopRunSpecific
方法睁宰,其中還有個(gè)1.0e10
這就是1*10^10
表示超時(shí)時(shí)間。
3.2 CFRunLoopRunSpecific
CFRunLoopRunSpecific
源碼如下:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 根據(jù)modeName獲取到mode
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;
// 通知 Observers:Runloop 即將進(jìn)入 loop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 調(diào)用 __CFRunLoopRun(真正run的地方) 進(jìn)入loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知 Observers: Runloop 即將退出
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
通過上面的代碼我們知道這里主要有如下步驟:
- 根據(jù)modeName找到對(duì)應(yīng)的mode
- 在
run
前發(fā)通知Observers
即將進(jìn)入loop
- 調(diào)用
__CFRunLoopRun
進(jìn)行真正的run
寝凌,也就是真正的進(jìn)入到runloop
- 執(zhí)行完
__CFRunLoopRun
后也會(huì)發(fā)生通知Observers
表示Runloop
即將退出
3.3 __CFRunLoopRun
再一次來到__CFRunLoopRun
柒傻,下面我們看點(diǎn)不一樣的,這里代碼太多了硫兰,感興趣的可以自行下載诅愚,下載地址。這里我們通過一份偽代碼來展示一下__CFRunLoopRun
的主要邏輯:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
/// 首先根據(jù)modeName找到對(duì)應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
/// 通知 Observers: RunLoop 即將進(jìn)入 loop劫映。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
/// 內(nèi)部函數(shù),進(jìn)入loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
/// 通知 Observers: RunLoop 即將退出刹前。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
/// 核心函數(shù)
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
/// 通知 Observers: 即將處理timer事件
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
/// 通知 Observers: 即將處理Source事件
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
/// 處理Blocks
__CFRunLoopDoBlocks(rl, rlm);
/// 處理sources0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
/// 處理sources0返回為YES
if (sourceHandledThisLoop) {
/// 處理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
/// 判斷有無端口消息(Source1)
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
/// 處理消息
goto handle_msg;
}
/// 通知 Observers: 即將進(jìn)入休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
/// 等待被喚醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
/// 通知 Observers: 被喚醒泳赋,結(jié)束休眠
__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 if (被Source1喚醒) {
/// 被Source1喚醒喇喉,處理Source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
/// 處理block
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
// 處理source
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
// 超時(shí)處理
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
// 被外部調(diào)用者強(qiáng)制停止了
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
// 自動(dòng)停止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
// 什么都沒有了祖今,結(jié)束了
retVal = kCFRunLoopRunFinished;
}
// 如果沒超時(shí),mode里沒空,loop也沒被停止千诬,那繼續(xù)loop耍目。
} while (0 == retVal);
return retVal;
}
所以我們可以知道這里主要的邏輯就是根據(jù)不同的事件源進(jìn)行不同的處理,當(dāng)runloop
休眠時(shí)徐绑,可以通過響應(yīng)的時(shí)間喚醒Runloop
邪驮。
還是放一份源碼吧,有點(diǎn)多傲茄,感興趣的慢慢看:
/* rl, rlm are locked on entrance and exit */
/**
* 運(yùn)行run loop
*
* @param rl 運(yùn)行的RunLoop對(duì)象
* @param rlm 運(yùn)行的mode
* @param seconds run loop超時(shí)時(shí)間
* @param stopAfterHandle true:run loop處理完事件就退出 false:一直運(yùn)行直到超時(shí)或者被手動(dòng)終止
* @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)啟動(dòng)后的CPU運(yùn)行時(shí)間毅访,用于控制超時(shí)時(shí)間
uint64_t startTSR = mach_absolute_time();
// 判斷當(dāng)前runloop的狀態(tài)是否關(guā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;
//判斷是否為主線程
Boolean 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_4CF(rlm->_queue);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
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;
// 1.0e10 == 1* 10^10
} else if (seconds <= TIMER_INTERVAL_LIMIT) {
//seconds為超時(shí)時(shí)間草巡,超時(shí)時(shí)執(zhí)行__CFRunLoopTimeout函數(shù)
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);
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(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);
dispatch_resume(timeout_timer);
} else { // infinite timeout
//永不超時(shí) - 永動(dòng)機(jī)
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
//標(biāo)志位默認(rèn)為true
Boolean didDispatchPortLastTime = true;
//記錄最后runloop狀態(tài)守呜,用于return
int32_t retVal = 0;
// itmes
do {
//初始化一個(gè)存放內(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
//取所有需要監(jiān)聽的port
__CFPortSet waitSet = rlm->_portSet;
//設(shè)置RunLoop為可以被喚醒狀態(tài)
__CFRunLoopUnsetIgnoreWakeUps(rl);
/// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)。
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources)
/// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)山憨。
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
/// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)弛饭。
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
}
//如果沒有Sources0事件處理 并且 沒有超時(shí),poll為false
//如果有Sources0事件處理 或者 超時(shí)萍歉,poll都為true
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//第一次do..whil循環(huán)不會(huì)走該分支侣颂,因?yàn)閐idDispatchPortLastTime初始化是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. 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息枪孩。
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.通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)。
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//設(shè)置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
//這里有個(gè)內(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_INFINITY);
//收到消息之后从撼,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) {
// 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);
#else
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
/// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息钧栖。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒低零。
/// ? 一個(gè)基于 port 的Source 的事件。
/// ? 一個(gè) Timer 到時(shí)間了
/// ? RunLoop 自身的超時(shí)時(shí)間到了
/// ? 被其他什么調(diào)用者手動(dòng)喚醒
// mach 事務(wù) - 指令
__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);
/// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了拯杠。
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
/// 收到消息掏婶,處理消息。
handle_msg:;
__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
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// 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();
/// 9.1 如果一個(gè) Timer 到時(shí)間了潭陪,觸發(fā)這個(gè)Timer的回調(diào)臼寄。
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) {
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
/// 9.2 如果有dispatch到main_queue的block,執(zhí)行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
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
/// 9.3 如果一個(gè) Source1 (基于port) 發(fā)出事件了钧忽,處理這個(gè)事件
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
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 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
/// 執(zhí)行加入到Loop的block
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 進(jìn)入loop時(shí)參數(shù)說處理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
/// 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
/// 被外部調(diào)用者強(qiáng)制停止了
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
/// 自動(dòng)停止了
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
/// source/timer/observer一個(gè)都沒有了
retVal = kCFRunLoopRunFinished;
}
/// 如果沒超時(shí)瘟则,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;
}
綜上所述,我們通過一個(gè)流程圖來總結(jié)一下Runloop: