概述
RunLoop提供了一種機制:線程沒有任務(wù)執(zhí)行時,進入休眠狀態(tài)走贪,讓出CPU資源浪讳;當(dāng)有任務(wù)需要執(zhí)行的時候缰盏,喚醒線程。
Android的Looper淹遵、Nodejs的Event Loop都是類似的原理口猜。
基礎(chǔ)用法
創(chuàng)建runloop
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建線程,并調(diào)用run1方法執(zhí)行任務(wù)
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
// 開啟線程
[self.thread start];
}
- (void) run1
{
// 這里寫任務(wù)
NSLog(@"----run1-----");
// 添加下邊兩句代碼透揣,就可以開啟RunLoop济炎,之后self.thread就變成了常駐線程,可隨時添加任務(wù)辐真,并交于RunLoop處理
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
// 測試是否開啟了RunLoop须尚,如果開啟RunLoop崖堤,則來不了這里,因為RunLoop開啟了循環(huán)耐床。
NSLog(@"未開啟RunLoop");
}
原理
組成部分
和runloop有關(guān)的幾個名詞:Thread密幔、CFRunLoopRef、CFRunLoopModeRef撩轰、CFRunLoopSourceRef胯甩、CFRunLoopTimerRef、CFRunLoopObserverRef
他們的關(guān)系是如下圖所示
1.Thread和runloop是一一對應(yīng)的钧敞。iOS主線程中默認創(chuàng)建了Runloop蜡豹,使主線程一直處于運行或休眠狀態(tài),沒有被系統(tǒng)銷毀溉苛。子線程默認是沒有runloop的镜廉,所以子線程執(zhí)行完任務(wù)后,會被系統(tǒng)銷毀愚战〗课ǎ可以手動在子線程中創(chuàng)建runloop,使子線程處于奔帕幔活狀態(tài)塔插。詳見基礎(chǔ)用法部分。
2.CFRunLoopRef代表runloop對象拓哟。創(chuàng)建runloop和runloop的方法詳見基礎(chǔ)用法部分想许。
3.CFRunLoopModeRef是runloop的運行模式。Runloop可以包含若干個Mode断序,每個Mode又包含Source/Timer/Observer流纹。當(dāng)切換Mode時必須退出當(dāng)前Mode,然后重新進入Runloop以保證不同Mode的Source/Timer/Observer互不影響违诗。 系統(tǒng)系統(tǒng)的Mode有:NSDefaultRunLoopMode漱凝、UITrackingRunLoopMode、UIInitializationRunLoopMode诸迟、GSEventReceiveRunLoopMode kCFRunLoopCommonModes茸炒。其中UIInitializationRunLoopMode是剛啟動App時第進入的第一個 Mode,啟動完成后就不再使用阵苇。NSDefaultRunLoopMode是默認的Mode壁公,UITrackingRunLoopMode是跟蹤用戶交互的Mode。kCFRunLoopCommonModes不是一種具體的Mode绅项,是而是一種模式組合贮尖,在iOS系統(tǒng)中默認包含了NSDefaultRunLoopMode和 UITrackingRunLoopMode。
4.CFRunLoopSourceRef是RunLoop的事件源趁怔。蘋果文檔將RunLoop能夠處理的事件分為Input sources和timer事件湿硝。
Input sources分為source0和source1兩大類。其中source0主要處理應(yīng)用層的事件(不是內(nèi)核或其他進程發(fā)過來的)润努,如:performSelectors关斜、dispatch_async。Source1是基于mach_Port的,處理來自系統(tǒng)內(nèi)核或者其他進程或線程的事件铺浇,可以主動喚醒休眠中的RunLoop痢畜。mach_port大家就理解成進程間相互發(fā)送消息的一種機制就好, 比如屏幕點擊, 網(wǎng)絡(luò)數(shù)據(jù)的傳輸都會觸發(fā)sourse1。
簡單舉個例子:一個APP在前臺靜止著鳍侣,此時丁稀,用戶用手指點擊了一下APP界面,那么過程就是下面這樣的:
我們觸摸屏幕,先摸到硬件(屏幕)倚聚,屏幕表面的事件會被IOKit先包裝成Event,通過mach_Port傳給正在活躍的APP , Event先告訴source1(mach_port),source1喚醒RunLoop, 然后將事件Event分發(fā)給source0,然后由source0來處理线衫。
5.CFRunLoopTimerRef 定時器。在主線程的RunLoop的NSDefaultRunLoopMode中添加一個NSTimer惑折,然后滑動列表授账,會發(fā)現(xiàn)定時器不工作。是因為滑動列表后惨驶,主線程的Mode切換到UITrackingRunLoopMode白热,而定時器在NSDefaultRunLoopMode,所以定時器不工作粗卜∥萑罚可以把NSTimer添加到UITrackingRunLoopMode或者kCFRunLoopCommonModes中。
6.CFRunLoopObserverRef RunLoop狀態(tài)的監(jiān)聽者续扔」ネ危可以監(jiān)聽
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry , // 進入 loop
kCFRunLoopBeforeTimers , // 觸發(fā) Timer 回調(diào)
kCFRunLoopBeforeSources , // 觸發(fā) Source0 回調(diào)
kCFRunLoopBeforeWaiting , // 等待 mach_port 消息
kCFRunLoopAfterWaiting ), // 接收 mach_port 消息
kCFRunLoopExit , // 退出 loop
kCFRunLoopAllActivities // loop 所有狀態(tài)改變
}
監(jiān)聽的代碼
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
{
NSLog(@"zizhong,進入runloop");
}
break;
case kCFRunLoopBeforeTimers:
{
NSLog(@"zizhong,timers");
}
break;
case kCFRunLoopBeforeSources:
{
NSLog(@"zizhong,sources");
}
break;
case kCFRunLoopBeforeWaiting:
{
NSLog(@"zizhong,即將進入休眠");
}
break;
case kCFRunLoopAfterWaiting:
{
NSLog(@"zizhong,喚醒");
}
break;
case kCFRunLoopExit:
{
NSLog(@"zizhong,退出");
}
break;
default:
break;
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
運行原理
runloop偽代碼
int32_t __CFRunLoopRun()
{
// 通知即將進入runloop
__CFRunLoopDoObservers(KCFRunLoopEntry);
do
{
// 通知將要處理timer和source
__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(kCFRunLoopBeforeSources);
// 處理非延遲的主線程調(diào)用
__CFRunLoopDoBlocks();
// 處理Source0事件
__CFRunLoopDoSource0();
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks();
}
/// 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個 Source1 然后跳轉(zhuǎn)去處理消息测砂。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort();
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的線程即將進入休眠(sleep)茵烈。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
// GCD dispatch main queue
CheckIfExistMessagesInMainDispatchQueue();
// 即將進入休眠
__CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
// 等待內(nèi)核mach_msg事件
mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts();
// 等待。砌些。呜投。
// 從等待中醒來
__CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
// 處理因timer的喚醒
if (wakeUpPort == timerPort)
__CFRunLoopDoTimers();
// 處理異步方法喚醒,如dispatch_async
else if (wakeUpPort == mainDispatchQueuePort)
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
// 處理Source1
else
__CFRunLoopDoSource1();
// 再次確保是否有同步的方法需要調(diào)用
__CFRunLoopDoBlocks();
} while (!stop && !timeout);
// 通知即將退出runloop
__CFRunLoopDoObservers(CFRunLoopExit);
}
高級用法
點擊事件響應(yīng)
(1)用戶觸發(fā)事件->(2)系統(tǒng)將事件轉(zhuǎn)交到對應(yīng)APP的事件隊列->(3)APP從消息隊列頭取出事件->(4)交由Main Window進行消息分發(fā)->(5)找到合適的Responder進行處理,如果沒找到存璃,則會沿著Responder chain返回到APP層仑荐,丟棄不響應(yīng)該事件
用戶觸發(fā)事件, IOKit.framework 生成一個 IOHIDEvent 事件并由 SpringBoard 接收纵东,SpringBoard會利用mach port粘招,產(chǎn)生source1,來喚醒目標(biāo)APP的com.apple.uikit.eventfetch-thread的RunLoop偎球。Eventfetch thread會將main runloop 中__handleEventQueue所對應(yīng)的source0設(shè)置為signalled == Yes狀態(tài)洒扎,同時喚醒main RunLoop辑甜。mainRunLoop則調(diào)用__handleEventQueue進行事件隊列處理。
dispatch_async(dispatch_get_main_queue(), block)
如當(dāng)調(diào)用了 dispatch_async(dispatch_get_main_queue(), block)時袍冷,主隊列會把該 block 放到對應(yīng)的線程(恰好是主線程)中磷醋,主線程的 RunLoop 會被喚醒,從消息中取得這個 block胡诗,回調(diào) CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 來執(zhí)行這個 block
線程钡讼撸活
子線程默認是完成任務(wù)后結(jié)束。當(dāng)要經(jīng)常使用子線程煌恢,每次開啟子線程比較耗性能骇陈。此時可以開啟子線程的 RunLoop,保持 RunLoop 運行瑰抵,則使子線程保持不死你雌。AFNetworking 基于 NSURLConnection 時正是這樣做的,希望在后臺線程能保持活著谍憔,從而能接收到 delegate 的回調(diào)匪蝙。
/* 返回一個線程 */
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
// 創(chuàng)建一個線程,并在該線程上執(zhí)行下一個方法
_networkRequestThread = [[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
// 開啟線程
[_networkRequestThread start];
});
return _networkRequestThread;
}
/* 在新開的線程中執(zhí)行的第一個方法 */
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
// 獲取當(dāng)前線程對應(yīng)的 RunLoop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 為 RunLoop 添加 source习贫,模式為 DefaultMode
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
// 開始運行 RunLoop
[runLoop run];
/ /或者
//[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
}
}
監(jiān)聽卡頓
核心思路:主線程runloop中添加監(jiān)聽逛球,監(jiān)聽runloop狀態(tài)變化。如果runloop長期處于kCFRunLoopBeforeSources(處理source0)或者kCFRunLoopAfterWaiting(處理source1)苫昌,就說明出現(xiàn)了卡頓颤绕。