NSRunLoop簡(jiǎn)介
一. 什么是RunLoop
-
RunLoop
- 從字面上了解, RunLoop即是運(yùn)行循環(huán), 就像是在一個(gè)圓形循環(huán)中去運(yùn)作
- RunLoop的基本作用
- 他是App持續(xù)運(yùn)行的保證, 如果RunLoop不存在了, 程序也就終止運(yùn)行了
- RunLoop會(huì)在循環(huán)中處理App的各種事件, 如
觸摸事件, 定時(shí)器事件, Selector事件
- RunLoop最大的優(yōu)勢(shì)就是能節(jié)省CPU的資源, 提高程序的性能, 他會(huì)在需要執(zhí)行任務(wù)的時(shí)候被喚醒, 當(dāng)沒有任務(wù)執(zhí)行的時(shí)候進(jìn)入休眠狀態(tài)
-
Main函數(shù)中的RunLoop
首先, 重溫一遍App的啟動(dòng)原理
當(dāng)Main函數(shù)執(zhí)行到UIApplicationMain時(shí), 就開啟了RunLoop運(yùn)行循環(huán)
在運(yùn)行循環(huán)開啟時(shí), 就會(huì)保證程序的持續(xù)運(yùn)行并且處理App的各種事件, 不會(huì)退出
-
Main函數(shù)中的RunLoop, 被稱為主運(yùn)行循環(huán), 而主運(yùn)行循環(huán)在整個(gè)App的聲明周期中都不會(huì)被銷毀, 他是程序運(yùn)行的保證
// 程序在啟動(dòng)時(shí)近尚,第一步就會(huì)執(zhí)行main函數(shù),在main函數(shù)中會(huì)執(zhí)行以下操作: int main(int argc, char * argv[]) { @autoreleasepool { /* nil:UIApplication類名或者子類名稱吊骤,如果為nil,就等于@"UIApplication" NSStringFromClass([AppDelegate class]:UIApplication代理的名稱 */ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } 程序啟動(dòng)的完整流程 1. 執(zhí)行main函數(shù) 2. 執(zhí)行UIApplicationMain函數(shù) 1> 指定UIApplication對(duì)象 2> 指定UIApplication的代理 3. 創(chuàng)建UIApplication對(duì)象跷究,并且指定他的代理 4. **創(chuàng)建一個(gè)事件循環(huán):主循環(huán)(RunLoop)蹬挺,并且是一個(gè)死循環(huán),保證程序的持續(xù)運(yùn)行** 5. 加載配置了所有應(yīng)用程序信息的info.plist文件 1> 判斷 Main storyboard file base name中有沒有指定Main沐序,即需要加載的StoryBoard文件 2> 如果指定了邑茄,就加載Main.storyboard 3> 如果沒有指定的話姨蝴,就會(huì)黑屏 6. 應(yīng)用程序啟動(dòng)完畢
二. NSRunLoop和CFRunLoopRef
-
簡(jiǎn)單介紹
- CFRunLoopRef是在CoreFoundation框架中的, 它的內(nèi)部API以及實(shí)現(xiàn), 都是純C語言編寫, 這些API都是現(xiàn)成安全的
- NSRunLoop是基于CFRunLoopRef的封裝, 他提供了面向?qū)ο蟮腁PI, 但是這些API不是線程安全的
- 目前CFRunLoopRef已經(jīng)開源了, 大家可以在官方文檔中查看: 友情提示: 需要很好的C語言功底
-
NSRunloop和CFRunLoopRef都是RunLoop對(duì)象, 他們的區(qū)別是
- 這兩個(gè)對(duì)象的地址不同, 因?yàn)樗麄兊膶?duì)象來自于完全不同的類
- CFRunLoop可以調(diào)用
getCfRunLoop
方法, 將NSRunLoop轉(zhuǎn)化為CFRunLoop - 線程: RunLoop在主線程中, 保持持續(xù)循環(huán)狀態(tài), 當(dāng)所有的事件處理結(jié)束, 就會(huì)進(jìn)入休眠狀態(tài)
- 當(dāng)外界傳入各種時(shí)間時(shí): Prot接口事件時(shí), RunLoop就會(huì)被喚醒, 處理相關(guān)的事件, 當(dāng)事件處理完畢時(shí), 會(huì)再次進(jìn)入休眠狀態(tài)
- UI交互事件
- PerformSelector: onThread: 讓線程執(zhí)行任務(wù)
- Timer: 定時(shí)器事件
RunLoop運(yùn)行圖解
-
簡(jiǎn)單的應(yīng)用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 1. 獲取當(dāng)前線程對(duì)應(yīng)的RunLoop對(duì)象 NSRunLoop *curRunLoop = [NSRunLoop currentRunLoop]; NSLog(@"%p", curRunLoop); // 2. 獲取主線程對(duì)應(yīng)的RunLoop對(duì)象 NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop]; NSLog(@"%p", mainRunLoop); /* Core Foundation */ // 1. 獲得當(dāng)前線程的RunLoop對(duì)象 CFRunLoopRef runloop = CFRunLoopGetCurrent(); NSLog(@"%p", runloop); // 2. 獲得主線程的RunLoop對(duì)象 CFRunLoopRef cfMainRunLoop = CFRunLoopGetMain(); NSLog(@"%p", cfMainRunLoop); // 從這里可以看出這兩種運(yùn)行循環(huán)是完全不同的對(duì)象 // NSRunLoop --> CFRunLoopRef NSLog(@"%p---%p", cfMainRunLoop, mainRunLoop.getCFRunLoop); // 開啟子線程,執(zhí)行task方法 [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil]; } - (void)task { /* 子線程和RunLoop 1. 每一個(gè)子線程肺缕,都對(duì)應(yīng)一個(gè)自己的RunLoop 2. 主線程的RunLoop在程序運(yùn)行的時(shí)候就已經(jīng)創(chuàng)建了左医,而子線程的RunLoop則需要手動(dòng)開啟 3. [NSRunLoop currentRunLoop],此方法會(huì)開啟一個(gè)新的RunLoop 4. RunLoop需要執(zhí)行run方法同木,來開啟浮梢,但如果RunLoop中沒有任何任務(wù),就會(huì)關(guān)閉 */ // 1. 當(dāng)前RunLoop NSLog(@"%p--%p", [NSRunLoop currentRunLoop], [NSRunLoop mainRunLoop]); // 2. 開啟一個(gè)新的RunLoop [[NSRunLoop currentRunLoop] run]; NSLog(@"tast---%@", [NSThread currentThread]); }
三. RunLoop與線程
- 每一條線程, 都有一個(gè)與之相對(duì)應(yīng)的RunLoop對(duì)象, 負(fù)責(zé)處理線程中的任務(wù)
- 線程的創(chuàng)建
- 主線程: RunLoop是在程序已經(jīng)啟動(dòng)的時(shí)候就創(chuàng)建好了, 當(dāng)程序關(guān)閉的時(shí)候主線程才被銷毀
- 子線程: 子線程需要手動(dòng)創(chuàng)建RunLoop, 并且手動(dòng)開啟, 當(dāng)沒有任務(wù)執(zhí)行時(shí), 該線程會(huì)被關(guān)閉, RunLoop被銷毀
- 子線程和RunLoop
子線程會(huì)單獨(dú)開啟RunLoop去執(zhí)行任務(wù)
子線程和RunLoop是一一對(duì)應(yīng)的關(guān)系, 每個(gè)子線程都有自己的RunLoop(但需要主動(dòng)創(chuàng)建)
-
創(chuàng)建子線程的RunLoop:
[NSRunloop currentRunLoop]
- 通過對(duì)CFRunLoop原碼的分析可以判斷出, 這個(gè)方法是懶加載獲取RunLoop對(duì)象的, 當(dāng)?shù)谝淮握{(diào)用這個(gè)方法時(shí), 他就會(huì)在對(duì)應(yīng)的線程中創(chuàng)建一個(gè)RunLoop, 并且保存到一個(gè)字典中便于隨時(shí)取出
- 也就是說, 如果不主動(dòng)去獲取RunLoop, 那么默認(rèn)是不會(huì)給子線程創(chuàng)建一個(gè)RunLoop的
子線程的RunLoop需要手動(dòng)開啟:
[[NSRunLoop currentRunLoop] run]
-
如果RunLoop內(nèi)部沒有任何任務(wù)需要去處理時(shí), 就會(huì)被關(guān)閉
// 全局的Dictionary彤路,key 是 pthread_t黔寇, value 是 CFRunLoopRef static CFMutableDictionaryRef loopsDic; // 訪問 loopsDic 時(shí)的鎖 static CFSpinLock_t loopsLock; // 獲取一個(gè) pthread 對(duì)應(yīng)的 RunLoop CFRunLoopRef _CFRunLoopGet(pthread_t thread) { OSSpinLockLock(&loopsLock); if (!loopsDic) { // 第一次進(jìn)入時(shí),初始化全局Dic斩萌,并先為主線程創(chuàng)建一個(gè) RunLoop缝裤。 loopsDic = CFDictionaryCreateMutable(); CFRunLoopRef mainLoop = _CFRunLoopCreate(); CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop); } // 直接從 Dictionary 里獲取。 CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread)); if (!loop) { // 取不到時(shí)颊郎,創(chuàng)建一個(gè) loop = _CFRunLoopCreate(); CFDictionarySetValue(loopsDic, thread, loop); // 注冊(cè)一個(gè)回調(diào)憋飞,當(dāng)線程銷毀時(shí),順便也銷毀其對(duì)應(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()); }
四. RunLoop的相關(guān)類
- RunLoop的運(yùn)行模式圖
- 一個(gè)RunLoop包含多個(gè)Mode, 而每個(gè)Mode又包含若干個(gè)Source/Timer/Observer
-
相關(guān)類
- RunLoop: RunLoop對(duì)象本身
- RunLoopMode: 即RunLoop的運(yùn)行模式
- 一個(gè)RunLoop至少要制定一個(gè)運(yùn)行模式, 當(dāng)運(yùn)行模式指定之后, 至少有一個(gè)Source或者Timer任務(wù)在執(zhí)行
- RunLoop啟動(dòng)之后, 只能指定一個(gè)運(yùn)行模式, 可以使用
currentMode
來獲取 - 如果要切換RunLoop的運(yùn)行模式, 就要先退出當(dāng)前的RunLoop, 重新指定Mode再次進(jìn)入運(yùn)行
- 系統(tǒng)默認(rèn)注冊(cè)的5個(gè)Mode
- kCFRunLoopDefaultMode: App的默認(rèn)Mode, 通常主線程是在這個(gè)Mode下運(yùn)行的
- UITrackingRunLoopMode: 界面跟蹤Mode, 用于ScrollView/TableView等追蹤觸摸滑動(dòng), 保證界面滑動(dòng)的時(shí)候不受其他Mode影響
- UIInitializationRunLoopMode: 當(dāng)App啟動(dòng)時(shí), 第一個(gè)進(jìn)入的Mode, 啟動(dòng)完成之后就不會(huì)再使用這個(gè)Mode
- GSEventReceiveRunLoopMode: 接收系統(tǒng)事件的內(nèi)部Mode, 通常由系統(tǒng)自動(dòng)管理
- kCFRunLoopCommonMode: 一個(gè)類似于占位的Mode, 并不是一個(gè)真正的Mode
-
Mode中的類
- CFRunLoopSourceRef: 事件源, 事件, 輸入等都屬于事件源, 他有兩個(gè)分類
- Source0, 非基于Port, 用戶觸發(fā)的事件, 例如點(diǎn)擊事件等
- Source1, 基于Port的事件, 他用于系統(tǒng)內(nèi)部與線程之間交互
- CFRunLoopTimerRef: 定時(shí)器事件
- NSTimer:
- 如果將NSTimer添加到子線程中, 需要先創(chuàng)建一個(gè)RunLoop, 然后再啟動(dòng)RunLoop
- NSTimer受到RunLoop的印象, 一般會(huì)有一些輕微的誤差, 所以對(duì)于精密計(jì)時(shí), GCD定時(shí)器較為精準(zhǔn)
- GCD定時(shí)器: 精確到納秒, 較為精準(zhǔn)
GCD定時(shí)器的創(chuàng)建步驟: 創(chuàng)建定時(shí)器 -> 設(shè)置定時(shí)器 -> 設(shè)置定時(shí)器的回調(diào)方法 -> 恢復(fù)定時(shí)器
-
GCD定時(shí)器一定要添加一個(gè)強(qiáng)引用, 否則會(huì)被立即釋放
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 1. 創(chuàng)建GCD定時(shí)器 /* DISPATCH_SOURCE_TYPE_TIMER 定時(shí)器 uintptr_t handle 描述信息 unsigned long mask 傳入0 dispatch_queue_t queue 定時(shí)器運(yùn)行的隊(duì)列姆吭,決定定時(shí)器在哪個(gè)線程中運(yùn)行 */ dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0)); // 2. 設(shè)置定時(shí)器 /* dispatch_source_t source, 定時(shí)器的對(duì)象 dispatch_time_t start, 定時(shí)器什么時(shí)候開始 uint64_t interval, 定時(shí)器多長(zhǎng)時(shí)間執(zhí)行一次 uint64_t leeway 精準(zhǔn)度榛做,0為絕對(duì)精準(zhǔn) */ dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC); // 3. 設(shè)置定時(shí)器的任務(wù) dispatch_source_set_event_handler(timer, ^{ NSLog(@"---GCD---%@", [NSThread currentThread]); }); // 4. 恢復(fù)定時(shí)器 dispatch_resume(timer); // 5. 強(qiáng)引用定時(shí)器,否則創(chuàng)建出來就會(huì)被釋放 self.timer = timer; }
- NSTimer:
- CFRunLoopObserverRef: 觀察者
觀察者可以觀察到RunLoop不同的運(yùn)行狀態(tài)
-
通過判斷RunLoop的運(yùn)行狀態(tài), 可以執(zhí)行一些操作
// 1. 創(chuàng)建監(jiān)聽者 /* CFAllocatorRef allocator 分配存儲(chǔ)空間 CFOptionFlags activities 要監(jiān)聽哪個(gè)狀態(tài)内狸,kCFRunLoopAllActivities監(jiān)聽所有狀態(tài) Boolean repeats 是否持續(xù)監(jiān)聽RunLoop的狀態(tài) CFIndex order 優(yōu)先級(jí)检眯,默認(rèn)為0 Block activity RunLoop當(dāng)前的狀態(tài) */ CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { /* kCFRunLoopEntry = (1UL << 0), 進(jìn)入工作 kCFRunLoopBeforeTimers = (1UL << 1), 即將處理Timers事件 kCFRunLoopBeforeSources = (1UL << 2), 即將處理Source事件 kCFRunLoopBeforeWaiting = (1UL << 5), 即將休眠 kCFRunLoopAfterWaiting = (1UL << 6), 被喚醒 kCFRunLoopExit = (1UL << 7), 退出RunLoop kCFRunLoopAllActivities = 0x0FFFFFFFU 監(jiān)聽所有事件 */ // 當(dāng)activity處于什么狀態(tài)的時(shí)候,調(diào)用一次 switch (activity) { case kCFRunLoopEntry: NSLog(@"進(jìn)入"); break; case kCFRunLoopBeforeTimers: NSLog(@"即將處理Timer事件"); break; case kCFRunLoopBeforeSources: NSLog(@"即將處理Source事件"); break; case kCFRunLoopBeforeWaiting: NSLog(@"即將休眠"); break; case kCFRunLoopAfterWaiting: NSLog(@"被喚醒"); break; case kCFRunLoopExit: NSLog(@"退出RunLoop"); break; default: break; } }); // 2. 給對(duì)應(yīng)的RunLoop添加一個(gè)監(jiān)聽者昆淡,并制定監(jiān)聽的是那種運(yùn)行模式 /* CFRunLoopRef rl 要添加監(jiān)聽者的RunLoop CFRunLoopObserverRef observer, 要添加的監(jiān)聽者 CFStringRef mode RunLoop的運(yùn)行模式 */ CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
- CFRunLoopSourceRef: 事件源, 事件, 輸入等都屬于事件源, 他有兩個(gè)分類