RunLoop,顧名思義就是運行循環的意思,是指程序在運行過程當中循環作一些事情。api
當咱們建立一個terminal項目的時候,此時的main函數中並無一個RunLoop。因此程序運行完main函數以後就退出了。數組
而一個iOS的application程序,默認在主線程開啓了一個RunLoop,這樣一個App就能夠處理一些計時器事件,滑動事件等,不會立刻退出。markdown
在iOS項目中每一條線程都對應着一個RunLoop對象,RunLoop存放在一個以線程做爲key的散列表中。app
主線程在建立的時候默認開啓RunLoop,而其餘子線程默認不開啓,可是會在第一次獲取RunLoop([NSRunLoop currentRunLoop]或者CFRunLoopGetCurrent()
)的時候建立。框架
通常狀況下RunLoop的生命週期跟隨線程,線程結束的時候RunLoop也會被銷燬。函數
iOS中提供了一套Foundation框架的NSRunLoop api和一套基於Core Foundation的CFRunLoopRef api來使用RunLoop。其中NSRunLoop是基於CFRunLoopRef作了一層OC的封裝。oop
在CFRunLoop的源碼中RunLoop的基本結構以下:atom
CFRunLoopRef是一個__CFRunLoop
的結構體,結構體中存放了許多mode相關的成員。spa
其中_currentMode
是CFRunLoopModeRef
類型的,它是一個__CFRunLoopMode
類型的結構體指針。RunLoop經過它來表徵RunLoop的運行狀態。線程
一個RunLoop中包含有許多Mode。_commonModes
是一個可變的集合,集合中存放了許多mode。RunLoop在運行的時候只能選擇一個Mode做爲當前RunLoop執行的狀態,也就是_currentMode
。
mode是CFRunLoopMode
類型的。而CFRunLoopMode
是經過typedf__CFRunLoopMode
獲得的。__CFRunLoopMode
中存放了處理觸摸事件的source0
、系統時間捕捉的source1
、處理計時器的timers
、監聽RunLoop狀態的observer
等。
另外,若是RunLoop須要切換運行狀態的時候必須先退出當前的Mode,才能進入新的Mode。若是當前Mode中全部的source
、timer
、observer
的時候RunLoop就會馬上退出。
常見的RunLoopMode有默認的modekCFRunLoopDefaultMode
、跟蹤界面(好比:保證滑動不受其餘mode影響)的UITrackingRunLoopMode
。另外在api中還有一個kCFRunLoopCommonModes
可是這並非一個真正的mode,它不存在於_commonModes
中,它只是一個標記。
RunLoop 的監聽器會監聽RunLoop的一些狀態:
/* Run Loop Observer Activities */ typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 即將進入runloop kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理計時器 kCFRunLoopBeforeSources = (1UL << 2), // 即將處理source kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠 kCFRunLoopAfterWaiting = (1UL << 6), // 即將從休眠中喚醒 kCFRunLoopExit = (1UL << 7), // 即將退出RunLoop kCFRunLoopAllActivities = 0x0FFFFFFFU // 所有狀態 }; 複製代碼
咱們能夠經過Xcode自帶的lldb 經過bt
命令查看函數調用棧找到RunLoop的入口函數
在CFRunLoop.c
文件中找到該函數,咱們發現它經過__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry)
監聽進入RunLoop。接着有一個__CFRunLoopRun
的函數調用,該函數中封裝了RunLoop處理事件的邏輯。
咱們只關注__CFRunLoopRun
主要代碼:咱們發現該函數中存在着一個do-while()
循環,當retVal==0
的時候循環持續進行,當retVal != 0
的時候,回返回給函數CFRunLoopRunSpecific
,它調用__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
退出RunLoop。
在__CFRunLoopRun
中主要的流程都在下圖中進行了描述。總結來講:
do-while()
,若是retVal == 0
則循環持續進行。不然返回給CFRunLoopRunSpecific
函數,退出RunLoop當咱們使用+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
建立一個計時器,而且頁面存在scrollView的時候。滑動scrollView計時器就會中止運行。這是由於一開始runloop存在於NSDefaultRunLoopMode
,當滑動事件響應的時候runloop會進入UITrackingRunLoopMode
模式處理滑動事件,全部timer就會失去處理。
咱們能夠使用+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
建立timer,而後將它放在一個NSRunLoopCommonModes
標記的模式下進行工做,timer是能夠t在_commonModes數組中存放的模式下工做的。這樣就解決了滑動事件和timer計時器事件衝突的問題。
當咱們建立一條線程的時候,這條線程並無一個RunLoop。當咱們第一次獲取RunLoop的時候這條線程中才會建立RunLoop。
因此建立一條一直存在的線程,咱們須要在線程中加入一個不會被回收的RunLoop,也就是讓do-while()
一直存在,也就是RunLoop一直有事情在處理,而retVal
不會爲不是0的其餘值。
實例代碼:
#import "SoCPermanentThread.h" @interface SoCPermanentThread () @property (nonatomic, strong) NSThread *thread; @end @implementation SoCPermanentThread - (instancetype)init { if (self = [super init]) { self.thread = [[NSThread alloc] initWithBlock:^{ CFRunLoopSourceContext context = {0}; CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); CFRelease(source); CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false); }]; [self.thread start]; } return self; } - (void)executeTask:(SoCPermenantThreadTask)task { if (!_thread || !task) return; [self performSelector:@selector(__task:) onThread:_thread withObject:task waitUntilDone:NO]; } - (void)stop { if (!_thread) return; [self performSelector:@selector(__stop) onThread:_thread withObject:nil waitUntilDone:YES]; } - (void)__task:(SoCPermenantThreadTask)task { task(); } - (void)__stop { CFRunLoopStop(CFRunLoopGetCurrent()); _thread = nil; } - (void)dealloc { [self stop]; } @end 複製代碼
上述代碼使用Core Foundation
實現的線程保活,其中重要的就是首先往RunLoop中添加Source保證RunLoop有事情能夠作,另外就是CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
這個方法的最後一個參數BOOL參數returnAfterSourceHandled
,其值爲flase
表明執行完函數(處理完source)不會返回,而true
則相反,表示執行完函數(處理完source)會當即返回。
本篇主要以Core Foundation
api 爲基礎(Core Foundation
開源)簡述了RunLoop的基本概念,和調用流程,因爲NSRunLoop
是基於CFRunLoop
作的OC封裝,其原理和流程都是同樣的。另外介紹了兩個使用RunLoop的案例。