一、簡介
RunLoop是一個對象,這個對象在循環(huán)中用來處理程序運(yùn)行過程中出現(xiàn)的各種事件(比如說觸摸事件、UI刷新事件、定時器事件、Selector事件),從而保持程序的持續(xù)運(yùn)行;而且在沒有事件處理的時候,會進(jìn)入睡眠模式,從而節(jié)省CPU資源,提高程序性能。
RunLoop的代碼邏輯:
詳細(xì)解釋請看這里
// 用DefaultMode啟動
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
- 這種模型通常被稱作 Event Loop。 Event Loop 在很多系統(tǒng)和框架里都有實現(xiàn),比如 Node.js 的事件處理,比如 Windows 程序的消息循環(huán),再比如 OSX/iOS 里的 RunLoop。實現(xiàn)這種模型的關(guān)鍵點(diǎn)在于:如何管理事件/消息,如何讓線程在沒有處理消息時休眠以避免資源占用、在有消息到來時立刻被喚醒。
- RunLoop管理了其需要處理的事件和消息,并提供了一個入口函數(shù)來執(zhí)行上面 Event Loop 的邏輯。線程執(zhí)行了這個函數(shù)后,就會一直處于這個函數(shù)內(nèi)部 "接受消息->等待->處理" 的循環(huán)中,直到這個循環(huán)結(jié)束(比如傳入 quit 的消息),函數(shù)返回。
二、RunLoop的深入分析
1. 從程序入口main函數(shù)開始
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
程序主線程一開始,就會一直跑,那么猜想其內(nèi)部一定是開啟了一個和主線程對應(yīng)的
RunLoop
并且可以看出函數(shù)返回的是一個int返回值的UIApplicationMain()函數(shù)
2. UIApplicationMain函數(shù)
UIKIT_EXTERN int UIApplicationMain
(int argc,
char *argv[],
NSString * __nullable principalClassName,
NSString * __nullable delegateClassName
);
我們發(fā)現(xiàn)它返回的是一個int類型的值,那么我們對main函數(shù)做一些修改:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"開始");
int re = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"結(jié)束");
return re;
}
}
運(yùn)行程序,我們發(fā)現(xiàn)只會打印開始,并不會打印結(jié)束,這再次說明在UIApplicationMain函數(shù)中,開啟了一個和主線程相關(guān)的RunLoop,導(dǎo)致UIApplicationMain不會返回,一直在運(yùn)行中,也就保證了程序的持續(xù)運(yùn)行。
3. 繼續(xù)學(xué)習(xí)CFRunLoopRef
RunLoop對象包括Fundation中的NSRunLoop對象和CoreFoundation中的CFRunLoopRef對象。因為Fundation框架是基于CoreFoundation的封裝,因此我們學(xué)習(xí)RunLoop還是要研究CFRunLoopRef 源碼。
獲取RunLoop對象
//Foundation
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
//Core Foundation
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象
1. 主線程獲取CFRunLoopRef源碼
// 創(chuàng)建字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 創(chuàng)建主線程 根據(jù)傳入的主線程創(chuàng)建主線程對應(yīng)的RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主線程 將主線程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
2. 創(chuàng)建與子線程相關(guān)聯(lián)的CFRunLoopRe源碼
蘋果不允許直接創(chuàng)建 RunLoop,它只提供了兩個自動獲取的函數(shù):CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。
// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
// 訪問 loopsDic 時的鎖
static CFSpinLock_t loopsLock;
// 獲取一個 pthread 對應(yīng)的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次進(jìn)入時,初始化全局Dic,并先為主線程創(chuàng)建一個 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
// 直接從 Dictionary 里獲取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
// 取不到時,創(chuàng)建一個
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
// 注冊一個回調(diào),當(dāng)線程銷毀時,順便也銷毀其對應(yīng)的 RunLoop。
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
return loop;
}
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}
3. CFRunloopRef與線程之間的關(guān)系
- 首先,iOS 開發(fā)中能遇到兩個線程對象: pthread_t 和 NSThread。過去蘋果有份文檔標(biāo)明了 NSThread 只是 pthread_t 的封裝,但那份文檔已經(jīng)失效了,現(xiàn)在它們也有可能都是直接包裝自最底層的 mach thread。蘋果并沒有提供這兩個對象相互轉(zhuǎn)換的接口,但不管怎么樣,可以肯定的是 pthread_t 和 NSThread 是一一對應(yīng)的。比如,你可以通過 pthread_main_np() 或 [NSThread mainThread] 來獲取主線程;也可以通過 pthread_self() 或 [NSThread currentThread] 來獲取當(dāng)前線程。CFRunLoop 是基于 pthread 來管理的。
- 從
CFRunLoopRef源碼可以看出,線程和 RunLoop 之間是一一對應(yīng)的,其關(guān)系是保存在一個全局的 Dictionary 里。線程剛創(chuàng)建時并沒有 RunLoop,如果你不主動獲取,那它一直都不會有。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時,RunLoop 的銷毀是發(fā)生在線程結(jié)束時。你只能在一個線程的內(nèi)部獲取其 RunLoop(主線程除外)。
[NSRunLoop currentRunLoop];方法調(diào)用時,會先看一下字典里有沒有存子線程相對用的RunLoop,如果有則直接返回RunLoop,如果沒有則會創(chuàng)建一個,并將與之對應(yīng)的子線程存入字典中。
. 總結(jié)來說. CFRunloopRef與線程之間的關(guān)系
- 線程在處理完自己的任務(wù)后一般會退出,為了實現(xiàn)線程不退出能夠隨時處理任務(wù)的機(jī)制被稱為EventLoop,node.js 的事件處理,windows程序的消息循環(huán),iOS、OSX的RunLoop都是這種機(jī)制。
- 線程和RunLoop是一一對應(yīng)的,關(guān)系保存在全局的字典里。
在主線程中,程序啟動時,系統(tǒng)默認(rèn)添加了有kCFRunLoopDefaultMode 和 UITrackingRunLoopMode兩個預(yù)置Mode的RunLoop,保證程序處于等待狀態(tài),如果接收到來自觸摸事件等,就會執(zhí)行任務(wù),否則處于休眠中。 - 線程創(chuàng)建時并沒有RunLoop,(主線程除外),RunLoop不能創(chuàng)建,只能主動獲取才會有。RunLoop的創(chuàng)建是在第一次獲取時,RunLoop的銷毀是發(fā)生在線程結(jié)束時。只能在一個線程中獲取自己和主線程的RunLoop。
Core Foundation中關(guān)于RunLoop的5個類
CFRunLoopRef //獲得當(dāng)前RunLoop和主RunLoop
CFRunLoopModeRef //運(yùn)行模式,只能選擇一種,在不同模式中做不同的操作
CFRunLoopSourceRef //事件源,輸入源
CFRunLoopTimerRef //定時器時間
CFRunLoopObserverRef //觀察者
** CFRunLoopModeRef**
詳細(xì)內(nèi)容請點(diǎn)擊這里
1.簡介:
每個CFRunLoopRef 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer。每次調(diào)用 RunLoop 的主函數(shù)時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode。如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進(jìn)入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。
CFRunLoopModeRef類并沒有對外暴露,只是通過 CFRunLoopRef 的接口進(jìn)行了封裝。他們的關(guān)系如下:
CFRunLoopRef獲取Mode的接口:
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);
2.CFRunLoopMode的類型
- kCFRunLoopDefaultMode
App的默認(rèn)Mode,通常主線程是在這個Mode下運(yùn)行
- UITrackingRunLoopMode:
界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響- UIInitializationRunLoopMode:
在剛啟動 App 時第進(jìn)入的第一個 Mode,啟動完成后就不再使用- GSEventReceiveRunLoopMode:
接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到- kCFRunLoopCommonModes:
這是一個占位用的Mode,作為標(biāo)記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一種真正的Mode
3.CFRunLoopMode 和 CFRunLoop 的結(jié)構(gòu)
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
CFRunLoopSourceRef
- 是事件產(chǎn)生的地方。Source有兩個版本:Source0 和 Source1。
- Source0 只包含了一個回調(diào)(函數(shù)指針),它并不能主動觸發(fā)事件。使用時,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標(biāo)記為待處理,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。
- Source1 包含了一個 mach_port 和一個回調(diào)(函數(shù)指針),被用于通過內(nèi)核和其他線程相互發(fā)送消息。這種 Source 能主動喚醒 RunLoop 的線程.
** CFRunLoopObserverRef**
CFRunLoopObserverRef是觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變。
我們直接來看代碼,給RunLoop添加監(jiān)聽者,監(jiān)聽其運(yùn)行狀態(tài):
//創(chuàng)建監(jiān)聽者
/*
第一個參數(shù) CFAllocatorRef allocator:分配存儲空間 CFAllocatorGetDefault()默認(rèn)分配
第二個參數(shù) CFOptionFlags activities:要監(jiān)聽的狀態(tài) kCFRunLoopAllActivities 監(jiān)聽所有狀態(tài)
第三個參數(shù) Boolean repeats:YES:持續(xù)監(jiān)聽 NO:不持續(xù)
第四個參數(shù) CFIndex order:優(yōu)先級,一般填0即可
第五個參數(shù) :回調(diào) 兩個參數(shù)observer:監(jiān)聽者 activity:監(jiān)聽的事件
*/
/*
所有事件
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醒來了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
// 給RunLoop添加監(jiān)聽者
/*
第一個參數(shù) CFRunLoopRef rl:要監(jiān)聽哪個RunLoop,這里監(jiān)聽的是主線程的RunLoop
第二個參數(shù) CFRunLoopObserverRef observer 監(jiān)聽者
第三個參數(shù) CFStringRef mode 要監(jiān)聽RunLoop在哪種運(yùn)行模式下的狀態(tài)
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
/*
CF的內(nèi)存管理(Core Foundation)
凡是帶有Create、Copy、Retain等字眼的函數(shù),創(chuàng)建出來的對象,都需要在最后做一次release
GCD本來在iOS6.0之前也是需要我們釋放的,6.0之后GCD已經(jīng)納入到了ARC中,所以我們不需要管了
*/
CFRelease(observer);