RunLoop是經過內部維護的事件循環來對事件/消息進行管理的一個對象git
沒有消息須要處理時,休眠以免資源佔用,狀態切換是從用戶態經過系統調用切換到內核態github
有消息處理時,馬上被喚醒,狀態切換是從內核態經過系統調用切換到用戶態數組
這裏有一個問題,咱們應用程序中的main函數爲何能夠保持無退出呢安全
實際上呢,在咱們的main函數中會調用UIApplicationMain函數,在這個函數中會啓動一個運行循環(也就是咱們所說的RunLoop),在這個運行循環中能夠處理不少事件,例如屏幕的點擊,滑動列表,或者網絡請求的返回等等,在處理完事件以後,會進入等待,在這個循環中,並非一個單純的for循環或者while循環,而是從用戶態到內核態的切換,以及再從內核態到用戶態的切換,這裏面的等待也不等於死循環,這裏面最重要的是狀態的切換bash
在OC中,系統爲咱們提供了兩個RunLoop,一個是CFRunLoop,另外一個是NSRunLoop,而NSRunLoop是對CFRunLoop的一個封裝,提供了面向對象的API,而且它們也分別屬於不一樣的框架,NSRunLoop是屬於Foundation框架,而CFRunLoop是屬於Core Foundation框架網絡
關於RunLoop的數據結構主要有三種:數據結構
CFRunLoop多線程
CFRunLoopModeapp
Source/Timer/Observer框架
pthread:表明的是線程,RunLoop與線程的關係是一一對應的
currentMode:是一個CFRunLoopMode這樣一個數據結構
modes:是一個包含CFRunLoopMode類型的集合(NSMutableSet<CFRunLoopMode*>)
commonModes:是一個包含NSString類型的集合(NSMutableSet<NSString*>)
commonModeItems:也是一個集合,在這個集合中包含多個元素,其中包括多個Observer,多個Timer,多個Source
name:名稱,例如NSDefaultRunLoopMode,因此說是經過這樣一個名稱來切換對應的模式,例如在上面的commonModes裏面都是名稱字符串,也就是說經過這些名稱來支持多種模式
source0:集合類型的數據結構
source1:集合類型的數據結構
obsevers:數組類型的數據結構
timers:數組類型的數據結構
source0:須要手動喚醒線程
source1:具有喚醒線程的能力
和NSTimer是toll-free bridge的(免費橋轉換)
咱們能夠經過註冊一些Observer來實現對RunLoop相關時間點的觀測
能夠觀測的時間點包括:
kCFRunLoopEntry:RunLoop的入口時機,RunLoop將要啓動的時候的回調通知
kCFRunLoopBeforeTimers:RunLoop將要處理Timer事件的時候
kCFRunLoopBeforeSources:RunLoop將要處理Source事件的時候
kCFRunLoopBeforeWaiting:RunLoop將要進入休眠的時候,將要進行用戶態到內核態的切換
kCFRunLoopAfterWaiting:RunLoop將要進入喚醒的時候,內核態到用戶態的切換後不久
kCFRunLoopExit:RunLoop退出的時候
在RunLoop中,假如在mode1中運行,那麼在mode2中事件的回調就會接收不到,RunLoop只接受在當前mode中的回調,那麼這裏有一個經典問題,當咱們在滑動列表時,爲何會出現cell上的定時器中止的狀況以及如何解決
由於在列表滑動的時候當前RunLoop的mode從Default切換到了Tracking,因此致使原來mode中的事件回調接收不到,想要解決即可將其加入commonModes中,下面咱們來看一下commonMode
CommonMode並非一個實際存在的模式
是同步Source/Timer/Observer到多個Mode中的一中技術方案
在RunLoop啓動以後會發送一個通知,來告知觀察者
將要處理Timer/Source0事件這樣一個通知的發送
處理Source0事件
若是有Source1要處理,這時會經過一個go to語句的實現來進行代碼邏輯的跳轉,處理喚醒是收到的消息
若是沒有Source1要處理,線程就將要休眠,同時發送一個通知,告訴觀察者
而後線程進入一個用戶態到內核態的切換,休眠,而後等待喚醒,喚醒的條件大約包括三種: 一、Source1
二、Timer事件
三、外部手動喚醒
線程剛被喚醒以後也要發送一個通知告訴觀察者,而後處理喚醒時收到的消息
回到將要處理Timer/Source0事件這樣一個通知的發送
而後再次進行上面步驟,這就是一個RunLoop的事件循環機制
這裏有一個這樣的問題:當咱們點擊一個app,從咱們點擊到程序啓動、程序運行再到程序殺死這個過程,系統都發生了什麼呢
實際上當咱們調用了main函數以後,會調用UIApplicationMain函數,在這個函數內部會啓動主線程的RunLoop,而後通過一系列的處理,最終主線程的RunLoop會處於一個休眠狀態,而後咱們此時若是點擊一下屏幕,會轉化成一個Source1來說咱們的主線程喚醒,而後當咱們殺死程序時,會調用RunLoop的退出,同時發送通知告訴觀察者
線程與RunLoop是一一對應的
本身建立的線程默認沒有RunLoop
爲當前線程開啓一個RunLoop
向該RunLoop中添加一個Port/Source等維持RunLoop的事件循環
啓動該RunLoop
請看下面的一個代碼邏輯
#import "WXObject.h"
static NSThread *thread = nil;
/** 是否繼續事件循環*/
static BOOL runAlways = YES;
@implementation WXObject
+ (NSThread *)threadForDispatch {
if (thread == nil) {
@synchronized (self) {
if (thread == nil) {
thread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequest) object:nil];
[thread setName:@"alwaysThread"];
//啓動線程
[thread start];
}
}
}
return thread;
}
+ (void)runRequest {
//建立一個Source
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
//建立RunLoop,同時向RunLoop的defaultMode下面添加Source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
//若是能夠運行
while (runAlways) {
@autoreleasepool {
//令當前RunLoop運行在defaultMode下
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
}
}
//某一時機,靜態變量runAlways變爲NO時,保證跳出RunLoop,線程推出
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
}
@end
複製代碼
首先咱們在這裏定義兩個全局靜態變量,一個是咱們自定義的線程thread,還有一個是用來控制是否事件循環
而後咱們建立線程,用@synchronized來保證線程安全,建立的時候添加入口方法,而後啓動線程,當線程調用start方法時,會調用下面入口方法
在這個方法中首先建立source,傳入一個上下文,而後建立RunLoop,同時向RunLoop的defaultMode下面添加Source,CFRunLoopGetCurrent()這個方法若是獲取不到就會建立一個RunLoop,而後添加到defaultMode中
經過咱們前面定義的靜態變量來進行判斷,若是能夠運行,就令當前RunLoop運行在defaultMode下,這裏用了一個自動釋放池,減少內存峯值消耗,這裏須要注意的是,若是咱們上面添加到的是defaultMode,這裏也須要運行在defaultMode中,不然會出現死循環
某一時機,靜態變量runAlways變爲NO時,保證跳出RunLoop,線程推出,釋放source
以上就是實現一個常駐線程的代碼邏輯