RunLoop的本質
RunLoop是通過內部維護的事件迴圈來對事件/訊息進行管理的一個物件
-
沒有訊息需要處理時,休眠以避免資源佔用,狀態切換是從使用者態通過系統呼叫切換到核心態
-
有訊息處理時,立刻被喚醒,狀態切換是從核心態通過系統呼叫切換到使用者態
這裡有一個問題,我們應用程式中的main函式為什麼可以保持無退出呢
實際上呢,在我們的main函式中會呼叫UIApplicationMain函式,在這個函式中會啟動一個執行迴圈(也就是我們所說的RunLoop),在這個執行迴圈中可以處理很多事件,例如螢幕的點選,滑動列表,或者網路請求的返回等等,在處理完事件之後,會進入等待,在這個迴圈中,並不是一個單純的for迴圈或者while迴圈,而是從使用者態到核心態的切換,以及再從核心態到使用者態的切換,這裡面的等待也不等於死迴圈,這裡面最重要的是狀態的切換
RunLoop的資料結構
在OC中,系統為我們提供了兩個RunLoop,一個是CFRunLoop,另一個是NSRunLoop,而NSRunLoop是對CFRunLoop的一個封裝,提供了物件導向的API,並且它們也分別屬於不同的框架,NSRunLoop是屬於Foundation框架,而CFRunLoop是屬於Core Foundation框架
關於RunLoop的資料結構主要有三種:
-
CFRunLoop
-
CFRunLoopMode
-
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:陣列型別的資料結構
CFRunLoopSource
-
source0:需要手動喚醒執行緒
-
source1:具備喚醒執行緒的能力
CFRunLoopTimer
和NSTimer是toll-free bridge的(免費橋轉換)
CFRunLoopObserver
我們可以通過註冊一些Observer來實現對RunLoop相關時間點的觀測
可以觀測的時間點包括:
-
kCFRunLoopEntry:RunLoop的入口時機,RunLoop將要啟動的時候的回撥通知
-
kCFRunLoopBeforeTimers:RunLoop將要處理Timer事件的時候
-
kCFRunLoopBeforeSources:RunLoop將要處理Source事件的時候
-
kCFRunLoopBeforeWaiting:RunLoop將要進入休眠的時候,將要進行使用者態到核心態的切換
-
kCFRunLoopAfterWaiting:RunLoop將要進入喚醒的時候,核心態到使用者態的切換後不久
-
kCFRunLoopExit:RunLoop退出的時候
RunLoop的mode
在RunLoop中,假如在mode1中執行,那麼在mode2中事件的回撥就會接收不到,RunLoop只接受在當前mode中的回撥,那麼這裡有一個經典問題,當我們在滑動列表時,為什麼會出現cell上的定時器停止的情況以及如何解決
因為在列表滑動的時候當前RunLoop的mode從Default切換到了Tracking,所以導致原來mode中的事件回撥接收不到,想要解決便可將其加入commonModes中,下面我們來看一下commonMode
CommonMode的特殊性
-
CommonMode並不是一個實際存在的模式
-
是同步Source/Timer/Observer到多個Mode中的一中技術方案
事件迴圈的實現機制
-
在RunLoop啟動之後會傳送一個通知,來告知觀察者
-
將要處理Timer/Source0事件這樣一個通知的傳送
-
處理Source0事件
-
如果有Source1要處理,這時會通過一個go to語句的實現來進行程式碼邏輯的跳轉,處理喚醒是收到的訊息
-
如果沒有Source1要處理,執行緒就將要休眠,同時傳送一個通知,告訴觀察者
-
然後執行緒進入一個使用者態到核心態的切換,休眠,然後等待喚醒,喚醒的條件大約包括三種: 1、Source1
2、Timer事件
3、外部手動喚醒 -
執行緒剛被喚醒之後也要傳送一個通知告訴觀察者,然後處理喚醒時收到的訊息
-
回到將要處理Timer/Source0事件這樣一個通知的傳送
-
然後再次進行上面步驟,這就是一個RunLoop的事件迴圈機制
這裡有一個這樣的問題:當我們點選一個app,從我們點選到程式啟動、程式執行再到程式殺死這個過程,系統都發生了什麼呢
實際上當我們呼叫了main函式之後,會呼叫UIApplicationMain函式,在這個函式內部會啟動主執行緒的RunLoop,然後經過一系列的處理,最終主執行緒的RunLoop會處於一個休眠狀態,然後我們此時如果點選一下螢幕,會轉化成一個Source1來講我們的主執行緒喚醒,然後當我們殺死程式時,會呼叫RunLoop的退出,同時傳送通知告訴觀察者
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
以上就是實現一個常駐執行緒的程式碼邏輯