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