一.Runloop是什么
通俗點(diǎn)來(lái)說(shuō)往堡,我們有一個(gè)線程,當(dāng)我們需要它處理事件時(shí)翠拣,它要隨時(shí)啟動(dòng)版仔,我們不需要它時(shí),它要隨時(shí)待命误墓,如果我們每次運(yùn)行完畢之后這個(gè)線程直接關(guān)閉了蛮粮,下一次運(yùn)行需要再重新創(chuàng)建一個(gè)新的線程,那無(wú)疑會(huì)大大的消耗CPU的性能优烧。因此我們就需要一種機(jī)制蝉揍,讓線程隨時(shí)待命,執(zhí)行任務(wù)完畢進(jìn)入休眠狀態(tài)畦娄,等待下一次喚醒又沾,在macOS
和iOS
系統(tǒng)中,就是通過(guò)Runloop來(lái)對(duì)線程進(jìn)行管理的熙卡。
二.Runloop的基本作用
1.保證程序的持續(xù)運(yùn)行杖刷,程序一啟動(dòng)就會(huì)創(chuàng)建主線程,主線程一開(kāi)始執(zhí)行就會(huì)創(chuàng)建對(duì)應(yīng)的Runloop
驳癌,保證線程不會(huì)被銷毀滑燃,使其平穩(wěn)運(yùn)行。
2.處理APP中的各種事件颓鲜,比如我們的觸摸事件表窘,定時(shí)器事件,selector事件甜滨。
3.節(jié)省CPU資源乐严,提高程序性能,當(dāng)程序運(yùn)行起來(lái)的時(shí)候衣摩,線程沒(méi)有接收到任何任務(wù)昂验,其對(duì)應(yīng)的Runloop
就會(huì)告知CPU
,我現(xiàn)在沒(méi)事可做艾扮,我要去休息既琴,當(dāng)有事件產(chǎn)生時(shí),需要這個(gè)線程去執(zhí)行泡嘴,Runloop
就會(huì)立刻告之其所對(duì)應(yīng)的線程去執(zhí)行任務(wù)甫恩。
從圖中我們可以看到,當(dāng)有事件產(chǎn)生酌予,或者定時(shí)器方法需要執(zhí)行時(shí)填物,Runloop就會(huì)將對(duì)應(yīng)的任務(wù)安排給相應(yīng)的處理方去執(zhí)行纹腌。
三.Runloop基本源碼
我們先來(lái)看一下Runloop的基本執(zhí)行源碼
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è)非常簡(jiǎn)單的do-while
循環(huán),CFRunLoopRun
會(huì)根據(jù)result
來(lái)判斷是否要繼續(xù)執(zhí)行下去滞磺,當(dāng)result
為停止或者結(jié)束時(shí)升薯,Runloop
便會(huì)跳出循環(huán),否則就會(huì)繼續(xù)執(zhí)行击困。
四.Runloop對(duì)象
Runloop在Cocoa Foundation
中是NSRunLoop
對(duì)象涎劈,而在Core Foundation
中則是 CFRunLoopRef
對(duì)象,我們解讀的源碼是CFRunLoopRef
阅茶,Runloop的代碼是開(kāi)源的蛛枚,可以前往蘋(píng)果官網(wǎng)的地址進(jìn)行下載:https://opensource.apple.com/tarballs/
如何獲取Runloop對(duì)象呢?蘋(píng)果是不允許
直接創(chuàng)建Runloop
對(duì)象的脸哀,但是給我們提供了相應(yīng)的方法獲取線程的Runloop
對(duì)象
Foundation
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對(duì)象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對(duì)象
Core Foundation
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對(duì)象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對(duì)象
五.線程與Runloop的關(guān)系
以前蘋(píng)果的多線程有兩種pthread_t
和 NSThread
蹦浦,pthread_t
和 NSThread
是一一對(duì)應(yīng)的。比如撞蜂,你可以通過(guò) pthread_main_thread_np()
或 [NSThread mainThread]
來(lái)獲取主線程盲镶;也可以通過(guò)pthread_self()
或 [NSThread currentThread]
來(lái)獲取當(dāng)前線程。CFRunLoop
是基于pthread
來(lái)管理的蝌诡。我們先看一下Runloop的源碼溉贿。
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
//程序第一次進(jìn)來(lái)判斷是否存在runloop
if (!__CFRunLoops) {
//不存在runloop
__CFUnlock(&loopsLock);
//創(chuàng)建一個(gè)字典,用于管理保存線程以及其對(duì)應(yīng)的runloop
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//使用主線程創(chuàng)建一個(gè)主循環(huán)(mainLoop)
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);
}
//從字典中獲取runloop,使用傳進(jìn)來(lái)的線程作為key來(lái)獲取
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
//如果沒(méi)取到runloop
if (!loop) {
//新建一個(gè)runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
//新建runloop成功颁湖,將runloop存入字典
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;
}
通過(guò)源碼可以得出以下結(jié)論宣蠕,每條線程都有一個(gè)與之一一對(duì)應(yīng)的Runloop,線程和Runloop會(huì)保存在一個(gè)全局字典里甥捺,系統(tǒng)會(huì)自動(dòng)創(chuàng)建主線程的Runloop抢蚀,子線程的Runloop則需要手動(dòng)創(chuàng)建,通過(guò)[NSRunLoop currentRunLoop]
涎永,Runloop在獲取時(shí)創(chuàng)建思币,在線程結(jié)束時(shí)銷毀鹿响。
六.Runloop的結(jié)構(gòu)體
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;
};
其實(shí)很多我們平時(shí)難以理解的概念羡微,一旦進(jìn)入到結(jié)構(gòu)體層面去分析就會(huì)變得很好理解,比如Block原理惶我,分類為什么不能添加屬性等問(wèn)題妈倔,去看一下相關(guān)源碼立刻就會(huì)恍然大悟,因此我個(gè)人在學(xué)習(xí)的時(shí)候也很熱衷于閱讀相關(guān)源碼绸贡,那么現(xiàn)在就讓我們來(lái)看一下Runloop的結(jié)構(gòu)體盯蝴。
先來(lái)看一下關(guān)鍵的成員變量
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
CFRunLoopModeRef
其實(shí)是指向__CFRunLoopMode
結(jié)構(gòu)體的指針毅哗,我們?cè)倏匆幌?code>CFRunLoopMode的結(jié)構(gòu)體
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 */
};
照例篩選出最關(guān)鍵的幾個(gè)成員變量
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
Runloop
在進(jìn)行運(yùn)行的時(shí)候是會(huì)選擇對(duì)應(yīng)模式的,也就是__CFRunLoopMode
捧挺,每個(gè)Mode又有對(duì)應(yīng)的source0/source1/observers/timers
虑绵,每次Runloop
運(yùn)行的時(shí)候只能選擇一個(gè)Mode
作為currentMode
。
六.source0/source1/observers/timers
1.source0:處理的是App內(nèi)部的事件闽烙、App自己負(fù)責(zé)管理翅睛,如按鈕點(diǎn)擊事件等。
2.source1:由RunLoop和內(nèi)核管理黑竞,Mach Port驅(qū)動(dòng)捕发,如CFMachPort、CFMessagePort很魂。
我們可以通過(guò)點(diǎn)擊事件驗(yàn)證一下
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch * touch = touches.anyObject;//獲取觸摸對(duì)象
NSLog(@"%@",@(touch.tapCount));//短時(shí)間內(nèi)的點(diǎn)擊次數(shù)
}
在點(diǎn)擊方法中打斷點(diǎn)扎酷,然后在控制臺(tái)輸入bt即可看到完整的堆棧信息
同樣的performSelector
也會(huì)觸發(fā)source0,我們用代碼驗(yàn)證一下
[self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];
在test方法中打斷點(diǎn)遏匆,控制臺(tái)輸入bt查看堆棧信息
再來(lái)看一下timer
的驗(yàn)證法挨,調(diào)用NSTimer
方法
[NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
NSLog(@"NSTimer ---- timer調(diào)用了");
}];
控制臺(tái)驗(yàn)證
Observer:告知外界RunLoop
狀態(tài)的更改
七.Runloop相關(guān)類以及作用
在Runloop的.h
文件中我們可以看到,聲明了四個(gè)類
typedef struct __CFRunLoop * CFRunLoopRef;
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
而CFRunloop
中又包含有一個(gè)關(guān)鍵的Mode類
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
這五個(gè)類就是Runloop實(shí)現(xiàn)的最關(guān)鍵的類拉岁,我們分別來(lái)說(shuō)一下他們的作用
-
CFRunLoopModeRef坷剧,這個(gè)代表了
Runloop
的運(yùn)行模式,一個(gè)Runloop
包含若干個(gè)Mode
喊暖,每個(gè)Mode
又包含若干Source惫企,Timer,Observer
陵叽,每次Runloop
啟動(dòng)時(shí)狞尔,只能指定其中一個(gè)Mode
,這個(gè)Mode
被稱作CurrentMode
巩掺,如果需要切換Mode
偏序,只能退出RunLoop
,再重新指定一個(gè)Mode
進(jìn)入胖替,這樣做主要是為了分隔開(kāi)不同組的Source研儒、Timer、Observer
独令,讓其互不影響端朵。如果Mode
里沒(méi)有任何Source0/Source1/Timer/Observer
,RunLoop
會(huì)立馬退出燃箭。
一個(gè)Mode
可以有多個(gè)Source冲呢,Observer,Timer
招狸。但是必須有一個(gè)Source
或者Timer
敬拓,不然Mode
為空邻薯,Runloop
會(huì)直接退出。
系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode
1. kCFRunLoopDefaultMode:App的默認(rèn)Mode乘凸,通常主線程是在這個(gè)Mode下運(yùn)行
2. UITrackingRunLoopMode:界面跟蹤 Mode厕诡,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響
3. UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode营勤,啟動(dòng)完成后就不再使用木人,會(huì)切換到kCFRunLoopDefaultMode
4. GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
5. kCFRunLoopCommonModes: 這是一個(gè)占位用的Mode冀偶,作為標(biāo)記kCFRunLoopDefaultMode和UITrackingRunLoopMode用醒第,并不是一種真正的Mode
有一個(gè)老生常談的問(wèn)題,我們使用NSTimer
定時(shí)器进鸠,每隔一段時(shí)間執(zhí)行一些事件稠曼,在這個(gè)過(guò)程中我們滑動(dòng)ScrollView
,定時(shí)器是會(huì)收到影響的客年。
因?yàn)槿绻覀冊(cè)谥骶€程使用定時(shí)器霞幅,此時(shí)RunLoop的Mode
為kCFRunLoopDefaultMode
,即定時(shí)器屬于kCFRunLoopDefaultMode
量瓜,那么此時(shí)我們滑動(dòng)ScrollView
時(shí)司恳,RunLoop
的Mode
會(huì)切換到UITrackingRunLoopMode
,因此在主線程的定時(shí)器就不在管用了绍傲,調(diào)用的方法也就不再執(zhí)行了扔傅,當(dāng)我們停止滑動(dòng)時(shí),RunLoop的Mode
切換回kCFRunLoopDefaultMode
烫饼,所以NSTimer
就又管用了猎塞。
如果我們希望在滑動(dòng)界面時(shí),計(jì)時(shí)器仍然有效杠纵,我們指定Runloop
的模式為kCFRunLoopCommonModes
即可荠耽,因?yàn)?code>kCFRunLoopCommonModes包含kCFRunLoopDefaultMode,kCFRunLoopDefaultMode
比藻。
-
CFRunLoopObserverRef是用來(lái)觀察
Runloop
運(yùn)行狀態(tài)的類铝量,看一下它的相關(guān)代碼
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//創(chuàng)建監(jiān)聽(tīng)者
/*
第一個(gè)參數(shù) CFAllocatorRef allocator:分配存儲(chǔ)空間 CFAllocatorGetDefault()默認(rèn)分配
第二個(gè)參數(shù) CFOptionFlags activities:要監(jiān)聽(tīng)的狀態(tài) kCFRunLoopAllActivities 監(jiān)聽(tīng)所有狀態(tài)
第三個(gè)參數(shù) Boolean repeats:YES:持續(xù)監(jiān)聽(tīng) NO:不持續(xù)
第四個(gè)參數(shù) CFIndex order:優(yōu)先級(jí),一般填0即可
第五個(gè)參數(shù) :回調(diào) 兩個(gè)參數(shù)observer:監(jiān)聽(tīng)者 activity:監(jiān)聽(tīng)的事件
*/
/*
所有事件
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7),// 即將退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop進(jìn)入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要處理Timers了");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop要處理Sources了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop要休息了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop醒來(lái)了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
// 給RunLoop添加監(jiān)聽(tīng)者
/*
第一個(gè)參數(shù) CFRunLoopRef rl:要監(jiān)聽(tīng)哪個(gè)RunLoop,這里監(jiān)聽(tīng)的是主線程的RunLoop
第二個(gè)參數(shù) CFRunLoopObserverRef observer 監(jiān)聽(tīng)者
第三個(gè)參數(shù) CFStringRef mode 要監(jiān)聽(tīng)RunLoop在哪種運(yùn)行模式下的狀態(tài)
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
/*
CF的內(nèi)存管理(Core Foundation)
凡是帶有Create银亲、Copy慢叨、Retain等字眼的函數(shù),創(chuàng)建出來(lái)的對(duì)象群凶,都需要在最后做一次release
GCD本來(lái)在iOS6.0之前也是需要我們釋放的插爹,6.0之后GCD已經(jīng)納入到了ARC中哄辣,所以我們不需要管了
*/
CFRelease(observer);
}
我們來(lái)看一下控制臺(tái)的輸出
可以看到Observer可以監(jiān)聽(tīng)Runloop的運(yùn)行流程请梢。
八.Runloop的處理邏輯
先來(lái)看看Runloop的簡(jiǎn)化版源碼赠尾,我們分成幾個(gè)部分,一步一步的看
//這個(gè)是Runloop的外部函數(shù)毅弧,內(nèi)部會(huì)調(diào)用底層的CFRunLoopRunSpecific方法
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
接下來(lái)我們?cè)倏匆幌?strong>CFRunLoopRunSpecific方法
// 經(jīng)過(guò)精簡(jiǎn)的 CFRunLoopRunSpecific 函數(shù)代碼气嫁,其內(nèi)部會(huì)調(diào)用__CFRunLoopRun函數(shù)
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// 通知Observers : 進(jìn)入Loop
// __CFRunLoopDoObservers內(nèi)部會(huì)調(diào)用 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
函數(shù)
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 核心的Loop邏輯
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers : 退出Loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
CFRunLoopRunSpecific函數(shù)中最關(guān)鍵的__CFRunLoopRun
函數(shù)源碼
// 精簡(jiǎn)后的 __CFRunLoopRun函數(shù),保留了主要代碼
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
// 通知Observers:即將處理Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知Observers:即將處理Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 處理Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 處理Sources0
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
// 處理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
// 如果有Sources1够坐,就跳轉(zhuǎn)到handle_msg標(biāo)記處
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
// 通知Observers:即將休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 進(jìn)入休眠寸宵,等待其他消息喚醒
__CFRunLoopSetSleeping(rl);
__CFPortSetInsert(dispatchPort, waitSet);
do {
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
} while (1);
// 醒來(lái)
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopUnsetSleeping(rl);
// 通知Observers:已經(jīng)喚醒
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg: // 看看是誰(shuí)喚醒了RunLoop,進(jìn)行相應(yīng)的處理
if (被Timer喚醒的) {
// 處理Timer
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
}
else if (被GCD喚醒的) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else { // 被Sources1喚醒的
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
}
// 執(zhí)行Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 根據(jù)之前的執(zhí)行結(jié)果元咙,來(lái)決定怎么做梯影,為retVal賦相應(yīng)的值
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;
}
} while (0 == retVal);
return retVal;
}
再附上一張流程圖九.Runloop的應(yīng)用
(1)AFNetworking:在子線程中的任務(wù)都執(zhí)行完畢之后,子線程就會(huì)被銷毀庶香,但是我們遇到一些情況甲棍,希望這個(gè)線程在程序運(yùn)行的過(guò)程中一直存在,就比如AFN
赶掖,網(wǎng)絡(luò)在運(yùn)行期間當(dāng)然要一直存在感猛,因此AFNetworking
中就使用了一個(gè)方式,使線程一直保持存在狀態(tài)奢赂。
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
(2)自動(dòng)釋放池:RunLoop
內(nèi)部有一個(gè)自動(dòng)釋放池陪白,當(dāng)RunLoop
開(kāi)啟時(shí),就會(huì)自動(dòng)創(chuàng)建一個(gè)自動(dòng)釋放池膳灶,當(dāng)RunLoop
在休息之前會(huì)釋放掉自動(dòng)釋放池的東西咱士,然后重新創(chuàng)建一個(gè)新的空的自動(dòng)釋放池,當(dāng)RunLoop
被喚醒重新開(kāi)始跑圈時(shí)轧钓,Timer,Source
等新的事件就會(huì)放到新的自動(dòng)釋放池中司致,當(dāng)RunLoop
退出的時(shí)候也會(huì)被釋放。
注意:只有主線程的RunLoop
會(huì)默認(rèn)啟動(dòng)聋迎。也就意味著會(huì)自動(dòng)創(chuàng)建自動(dòng)釋放池脂矫,子線程需要在線程調(diào)度方法中手動(dòng)添加自動(dòng)釋放池。
參考文章:iOS底層原理總結(jié)霉晕,深入理解RunLoop