1. RunLoop定義
RunLoop:運(yùn)行循環(huán),在程序運(yùn)行過程中循環(huán)做一些事情。所涉及的范疇包括:
① 定時(shí)器(Timer)、PerformSelector;
② GCD Async Main Queue;
③ 事件響應(yīng)、手勢(shì)識(shí)別、界面刷新;
④ 網(wǎng)絡(luò)請(qǐng)求;
⑤ AutoreleasePool。

通常情況下如果我們創(chuàng)建一個(gè)Command Line Tool項(xiàng)目,如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
程序在執(zhí)行到Line13之后,馬上會(huì)執(zhí)行Line15,然后程序退出,因?yàn)榇硕未a中沒有runloop.
但是在我們的iOS項(xiàng)目中并不是和Command Line Tool一樣寫main函數(shù)的.
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
我們可以通過runloop的源碼得知runloop的原理,所以我們模擬可以寫代碼模擬
int main(int argc, char * argv[]) {
@autoreleasepool {
int retVal = 0;
do {
// 睡眠中等待消息
int message = sleep_and_wait();
// 處理消息
retVal = process_message(message);
} while (0 == retVal);
return 0;
}
}
有了RunLoop,程序并不會(huì)馬上退出,而是保持運(yùn)行狀態(tài)。RunLoop可以處理App中的各種事件(比如觸摸事件、定時(shí)器事件等);RunLoop可以在該做事的時(shí)候做事,該休息的時(shí)候休息,從而節(jié)約CPU資源,提高程序性能。
iOS中提供兩套API來訪問和使用RunLoop,分別為Foundation框架下面的NSRunLoop和Core Foundation下面的CFRunLoopRef。
NSRunLoop和CFRunLoopRef都是RunLoop對(duì)象,其中NSRunLoop是基于CFRunLoopRef的一層OC包裝(參考圖NSRunLoop是基于CFRunLoopRef的一層OC包裝.png),CFRunLoopRef是開源的,開源地址。

上圖中,touch事件在主線程中觸發(fā),主線程所對(duì)應(yīng)的RunLoop為mainRunLoop,通過OC與C語言的API訪問RunLoop地址,可見兩者指針不同,但是NSLog(@"%@",[NSRunLoop currentRunLoop]);輸出的RunLoop詳細(xì)信息發(fā)現(xiàn)NSRunLoop本質(zhì)也是一個(gè)CFRunLoop,并且其地址與CFRunLoopGetMain()相同,進(jìn)而可以說明NSRunLoop是基于CFRunLoopRef的一層OC包裝。
2. RunLoop與線程的關(guān)系
平時(shí)都是通過:獲取當(dāng)前線程CFRunLoopGetCurrent();,獲取主線程CFRunLoopGetMain();.下面我們就來分析一下這兩個(gè)函數(shù)都做了什么,我們可以通過CFRunLoop源碼來看一下.
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
在調(diào)用CFRunLoopGetCurrent(),CFRunLoopGetMain()函數(shù)時(shí),函數(shù)內(nèi)部都會(huì)調(diào)用_CFRunLoopGet0(pthread_t t)函數(shù),并傳入不同的線程作為參數(shù)??梢娬{(diào)用CFRunLoopGetMain()時(shí),傳入的是主線程,調(diào)用CFRunLoopGetCurrent()時(shí)傳入的是當(dāng)前的線程(子線程或主線程)。接下來繼續(xù)看看_CFRunLoopGet0(pthread_t t)函數(shù):
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
透過_CFRunLoopGet0(pthread_t t)源碼,如果__CFRunLoops不存在,系統(tǒng)就會(huì)利用當(dāng)前的主線程"pthread_main_thread_np()"創(chuàng)建一個(gè)mainLoop,也就是“mainRunLoop”,然后將創(chuàng)建RunLoop的線程作為key,RunLoop作為value添加到一個(gè)全局的字典CFDictionarySetValue中。最后獲取"pthread_t"線程對(duì)應(yīng)的loop并返回loop。如果"pthread_t"不是主線程,用同樣的方法創(chuàng)建一個(gè)新的RunLoop,即"newLoop",同樣將"newLoop"存放到字典中然后返回newLoop。所以關(guān)于RunLoop與線程的關(guān)系可以總結(jié)如下:
- 每條線程都有唯一的一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象;
- RunLoop保存在一個(gè)全局的Dictionary里 ,線程作為key , Runloop作為value;
- 線程剛創(chuàng)建時(shí)并沒有RunLoop對(duì)象, RunLoop會(huì)在第一次獲取它時(shí)創(chuàng)建;
- RunLogp會(huì)在線程結(jié)束時(shí)銷毀;
- 主線程的RunLoop已經(jīng)自動(dòng)獲取(創(chuàng)建) ,子線程默認(rèn)沒有開啟Runloop。
3. RunLoop對(duì)象結(jié)構(gòu)
獲取RunLoop對(duì)象
//Foundation
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對(duì)象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對(duì)象
//Core Foundation
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對(duì)象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對(duì)象
我們可以用NSLog(@"%@",[NSRunLoop currentRunLoop]);方法打印Runloop對(duì)象,打印出RunLoop大概包含current mode、common modes、common mode items、modes這些信息。實(shí)際上從開源的文檔里,RunLoop包含的成員遠(yuǎn)不止上面這些,具體包含內(nèi)容如下圖:

其中比較重要的是"_modes"這個(gè)成員,它是一個(gè)集合類型,里面存放了多個(gè)CFRunLoopMode類型的mode成員,一個(gè)RunLoop可以有多個(gè)mode,這些mode都包含在"_modes"里。CFRunLoopMode結(jié)構(gòu)體如下:

CFRunLoopModeRef的定義
- CFRunLoopModeRef代表RunLoop的運(yùn)行模式;
- 一個(gè)RunLoop包含若干個(gè)Mode,每個(gè)Mode又包含若干個(gè)Source0/Source1/Timer/Observer;
- RunLoop啟動(dòng)時(shí)只能選擇其中一個(gè)Mode,作為currentMode
如果需要切換Mode,只能退出當(dāng)前Loop,再重新選擇一個(gè)Mode進(jìn)入:
?????①為什么要先退出再進(jìn)入;
?????②不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響;- 如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會(huì)立馬退出
4. CFRunLoopModeRef的常用的兩種model
kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行;
UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響;
其實(shí)還有一種特殊的模式kCFRunLoopCommonModes 的模式NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式 NSRunLoopCommonModes并不是一個(gè)真的模式,它只是一個(gè)標(biāo)記;
每一種mode里面都有source0、source1、observers、timers,他們主要負(fù)責(zé)的內(nèi)容如下:
- Source0
觸摸事件處理
performSelector:onThread:- Source1
基于Port的線程間通信
系統(tǒng)事件捕捉- Timers
NSTimer
performSelector:withobiect:afterDelay;- Observers
用于監(jiān)聽Runloop的狀態(tài)
UI刷新( BeforeWaiting)
Autorelease pool ( BeforeWaiting )
兩種方式監(jiān)聽RunLoop狀態(tài):
方式①:
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
switch (activity) {
case kCFRunLoopEntry: {
NSLog(@"kCFRunLoopEntry - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopBeforeTimers:{
NSLog(@"kCFRunLoopBeforeTimers - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopBeforeSources:{
NSLog(@"kCFRunLoopBeforeSources - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopBeforeWaiting:{
NSLog(@"kCFRunLoopBeforeWaiting - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopAfterWaiting:{
NSLog(@"kCFRunLoopAfterWaiting - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit: {
NSLog(@"kCFRunLoopExit - %@", mode);
CFRelease(mode);
break;
}
default:
break;
}
});
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 釋放
CFRelease(observer);
}
方式②:
void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry - %@", mode);
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers - %@", mode);
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources - %@", mode);
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting - %@", mode);
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting - %@", mode);
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit - %@", mode);
break;
default:
break;
}
CFRelease(mode);
}
- (void)viewDidLoad {
[super viewDidLoad];
// kCFRunLoopCommonModes默認(rèn)包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
// 創(chuàng)建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 釋放
CFRelease(observer);
}
CFRunLoopObserverRef的幾種狀態(tài)
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即將進(jìn)入Loop
kCFRunLoopBeforeTimers = (1UL << 1), //即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2), //即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), //即將推出Loop
KCFRunLoopAllActivities = 0x0FFFFFFU
}
想了解更多iOS學(xué)習(xí)知識(shí)請(qǐng)聯(lián)系:QQ(814299221)