文章主要分爲四個部分安全
一個線程一次只能執行一個任務,執行完成後線程就會退出。RunLoop 機制能讓線程隨時處理事件但並不退出。這裏說的隨時是指:程序須要運行時就保持程序的持續運行,不須要的時候就進入休眠狀態。網絡
NSRunLoop 和 CFRunLoopRef 都是和RunLoop 機制相關的類。CFRunLoopRef 基於 CoreFoundation 框架內,是純 C 函數的 API,全部這些 API 都是線程安全的。CFRunLoopRef 的代碼是開源的。NSRunLoop 是基於 CFRunLoopRef ,提供了面向對象的 API,可是這些 API 不是線程安全的。app
關於RunLoop 和線程之間的關係要知道如下幾點:框架
和 RunLoop 相關的主要涉及五個類:異步
RunLoop的結構函數
從上圖能夠看出,RunLoop 對象中能夠包含多個 Mode,每一個 Mode 又包含多個個 Source、Timer、Observer。oop
關於Mode首先要知道一個RunLoop 對象中可能包含多個Mode,且每次調用 RunLoop 的主函數時,只能指定其中一個 Mode(CurrentMode)。切換 Mode,須要從新指定一個 Mode 。主要是爲了分隔開不一樣的 Source、Timer、Observer,讓它們之間互不影響。測試
總共是有五種Mode:spa
kCFRunLoopDefaultMode
:默認模式,主線程是在這個運行模式下運行UITrackingRunLoopMode
:跟蹤用戶交互事件(用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其餘Mode影響)UIInitializationRunLoopMode
:在剛啓動App時第進入的第一個 Mode,啓動完成後就再也不使用GSEventReceiveRunLoopMode
:接受系統內部事件,一般用不到kCFRunLoopCommonModes
:僞模式,不是一種真正的運行模式,實際是kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
的結合。有這樣一個場景,假設本身封裝一個無限輪播視圖,頗有可能會出現這樣一種狀況:當你滑動輪播視圖時,輪播視圖的定時器再也不起做用,不能經過定時器調整UIScrollView的偏移值。之因此會出項上述現象,是由於主線程的 RunLoop 裏有兩個 Mode:kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
。默認狀況下是defaultMode
,可是當滑動UIScrollView
時,RunLoop 會將 mode 切換爲 TrackingRunLoopMode
,這時 Timer 就不會被回。若是想在滑動的時候不讓定時器失效,可使用CommonMode來解決。線程
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRunLoopSourceRef
CFRunLoopSourceRef是事件源,主要有兩種分類方式,一種是蘋果官方的分類方式,另外一種是按照函數調用棧棧分類方式。
2.3.1 官方分類
2.3.2 按照函數調用棧分類
CFRunLoopSourceSignal(source)
,將 Source 標記爲待處理,而後手動調用 CFRunLoopWakeUp(runloop)
喚醒 RunLoop,讓其處理這個事件。函數調用棧分類舉例
建立一個按鈕,添加點擊事件,並在按鈕回調事件添加斷點,當執行到斷點出左側會出現相關棧調用信息。從上圖能夠看出:點擊事件就是在Sources0
中處理的。至於 Source1
主要是用來接收、分發系統事件,而後再分發到Sources0
中處理。
CFRunLoopTimerRef
CFRunLoopTimerRef
是定時源,你能夠簡單把它理解爲NSTimer
。其包含一個時間點和一個回調(函數指針)。當被加入到 RunLoop 時,RunLoop 會註冊對應的時間點,當時間到時,RunLoop 會執行對應時間點的回調。
CFRunLoopObserverRef
CFRunLoopObserverRef
是觀察者,主要用來監聽RunLoop 的狀態,主要有如下幾種狀態。
能夠經過如下代碼驗證RunLoop的幾種狀態:
// 建立觀察者 CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { NSLog(@"監聽到RunLoop發生改變---%zd",activity); }); // 添加觀察者到當前RunLoop中 CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode); // 釋放observer CFRelease(observer);
RunLoop 邏輯流程
上圖是筆者從網上找到的一張 RunLoop 運行的相關流程邏輯圖。具體來講主要執行邏輯是這樣的:
藉助RunLoop能夠實現線程後臺常駐的功能,關鍵是在於兩行代碼,具體請看以下代碼。
- (void)viewDidLoad { [super viewDidLoad]; self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(runOne) object:nil]; [self.thread start]; } - (void) runOne{ NSLog(@"----任務1-----"); // 下面兩句代碼能夠實現線程保活 [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; // 測試是否開啓了RunLoop,若是開啓RunLoop,則來不了這裏,由於RunLoop開啓了循環。 NSLog(@"未開啓RunLoop"); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ // 利用performSelector,在self.thread的線程中調用run2方法執行任務 [self performSelector:@selector(runTwo) onThread:self.thread withObject:nil waitUntilDone:NO]; } - (void) runTwo{ NSLog(@"----任務2------"); }
實現了上述代碼以後,每次點擊屏幕都會打印----任務2------,這說明子線程處於活躍狀態。
在一些分析AFNetworking
源碼的文章中,也常常會出現以下這些代碼。其核心也是爲了實現線程後臺常駐。
+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } } + (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread; }
當後臺線程執行任務時,經過 performSelector:onThread:..
方法將任務放在後臺線程的 RunLoop 中。正常來講,一個線程執行完任務後就退出了。開啓runloop是爲了防止線程退出。一方面避免每次請求都要建立新的線程;另外一方面,由於connection 的請求是異步的,若是不開啓runloop,線程執行完代碼後不會等待網絡請求完的回調就退出了,這會致使網絡回調的代理方法不執行。
- (void)start { [self.lock lock]; if ([self isCancelled]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } else if ([self isReady]) { self.state = AFOperationExecutingState; [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } [self.lock unlock]; }
應用程序一旦啓動,主線程 RunLoop 裏註冊了兩個 Observer。一個 Observer 監聽即將進入Loop事件,回調內會調用 _objc_autoreleasePoolPush() 建立自動釋放池,並保證建立釋放池發生在其餘全部回調以前。另一個 Observer 監視了兩個事件(RunLoop即將進入休眠和即將退出 RunLoop 事件) ,前者會調用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池並建立新池;後者會調用 _objc_autoreleasePoolPop() 來釋放自動釋放池,並保證釋放自動釋放池事件發生在其它回調以後。