概述
RunLoop提供了一種機(jī)制:線程沒有任務(wù)執(zhí)行時(shí),進(jìn)入休眠狀態(tài),讓出CPU資源;當(dāng)有任務(wù)需要執(zhí)行的時(shí)候,喚醒線程。
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就變成了常駐線程,可隨時(shí)添加任務(wù),并交于RunLoop處理
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
// 測(cè)試是否開啟了RunLoop,如果開啟RunLoop,則來不了這里,因?yàn)镽unLoop開啟了循環(huán)。
NSLog(@"未開啟RunLoop");
}
原理
組成部分
和runloop有關(guān)的幾個(gè)名詞:Thread、CFRunLoopRef、CFRunLoopModeRef、CFRunLoopSourceRef、CFRunLoopTimerRef、CFRunLoopObserverRef
他們的關(guān)系是如下圖所示


1.Thread和runloop是一一對(duì)應(yīng)的。iOS主線程中默認(rèn)創(chuàng)建了Runloop,使主線程一直處于運(yùn)行或休眠狀態(tài),沒有被系統(tǒng)銷毀。子線程默認(rèn)是沒有runloop的,所以子線程執(zhí)行完任務(wù)后,會(huì)被系統(tǒng)銷毀??梢允謩?dòng)在子線程中創(chuàng)建runloop,使子線程處于?;顮顟B(tài)。詳見基礎(chǔ)用法部分。
2.CFRunLoopRef代表runloop對(duì)象。創(chuàng)建runloop和runloop的方法詳見基礎(chǔ)用法部分。
3.CFRunLoopModeRef是runloop的運(yùn)行模式。Runloop可以包含若干個(gè)Mode,每個(gè)Mode又包含Source/Timer/Observer。當(dāng)切換Mode時(shí)必須退出當(dāng)前Mode,然后重新進(jìn)入Runloop以保證不同Mode的Source/Timer/Observer互不影響。 系統(tǒng)系統(tǒng)的Mode有:NSDefaultRunLoopMode、UITrackingRunLoopMode、UIInitializationRunLoopMode、GSEventReceiveRunLoopMode kCFRunLoopCommonModes。其中UIInitializationRunLoopMode是剛啟動(dòng)App時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用。NSDefaultRunLoopMode是默認(rèn)的Mode,UITrackingRunLoopMode是跟蹤用戶交互的Mode。kCFRunLoopCommonModes不是一種具體的Mode,是而是一種模式組合,在iOS系統(tǒng)中默認(rèn)包含了NSDefaultRunLoopMode和 UITrackingRunLoopMode。
4.CFRunLoopSourceRef是RunLoop的事件源。蘋果文檔將RunLoop能夠處理的事件分為Input sources和timer事件。

Input sources分為source0和source1兩大類。其中source0主要處理應(yīng)用層的事件(不是內(nèi)核或其他進(jìn)程發(fā)過來的),如:performSelectors、dispatch_async。Source1是基于mach_Port的,處理來自系統(tǒng)內(nèi)核或者其他進(jìn)程或線程的事件,可以主動(dòng)喚醒休眠中的RunLoop。mach_port大家就理解成進(jìn)程間相互發(fā)送消息的一種機(jī)制就好, 比如屏幕點(diǎn)擊, 網(wǎng)絡(luò)數(shù)據(jù)的傳輸都會(huì)觸發(fā)sourse1。
簡(jiǎn)單舉個(gè)例子:一個(gè)APP在前臺(tái)靜止著,此時(shí),用戶用手指點(diǎn)擊了一下APP界面,那么過程就是下面這樣的:
我們觸摸屏幕,先摸到硬件(屏幕),屏幕表面的事件會(huì)被IOKit先包裝成Event,通過mach_Port傳給正在活躍的APP , Event先告訴source1(mach_port),source1喚醒RunLoop, 然后將事件Event分發(fā)給source0,然后由source0來處理。
5.CFRunLoopTimerRef 定時(shí)器。在主線程的RunLoop的NSDefaultRunLoopMode中添加一個(gè)NSTimer,然后滑動(dòng)列表,會(huì)發(fā)現(xiàn)定時(shí)器不工作。是因?yàn)榛瑒?dòng)列表后,主線程的Mode切換到UITrackingRunLoopMode,而定時(shí)器在NSDefaultRunLoopMode,所以定時(shí)器不工作??梢园袾STimer添加到UITrackingRunLoopMode或者kCFRunLoopCommonModes中。
6.CFRunLoopObserverRef RunLoop狀態(tài)的監(jiān)聽者??梢员O(jiān)聽
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry , // 進(jìn)入 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,進(jìn)入runloop");
}
break;
case kCFRunLoopBeforeTimers:
{
NSLog(@"zizhong,timers");
}
break;
case kCFRunLoopBeforeSources:
{
NSLog(@"zizhong,sources");
}
break;
case kCFRunLoopBeforeWaiting:
{
NSLog(@"zizhong,即將進(jìn)入休眠");
}
break;
case kCFRunLoopAfterWaiting:
{
NSLog(@"zizhong,喚醒");
}
break;
case kCFRunLoopExit:
{
NSLog(@"zizhong,退出");
}
break;
default:
break;
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
運(yùn)行原理

runloop偽代碼
int32_t __CFRunLoopRun()
{
// 通知即將進(jìn)入runloop
__CFRunLoopDoObservers(KCFRunLoopEntry);
do
{
// 通知將要處理timer和source
__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(kCFRunLoopBeforeSources);
// 處理非延遲的主線程調(diào)用
__CFRunLoopDoBlocks();
// 處理Source0事件
__CFRunLoopDoSource0();
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks();
}
/// 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort();
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
// GCD dispatch main queue
CheckIfExistMessagesInMainDispatchQueue();
// 即將進(jìn)入休眠
__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);
}
高級(jí)用法
點(diǎn)擊事件響應(yīng)
(1)用戶觸發(fā)事件->(2)系統(tǒng)將事件轉(zhuǎn)交到對(duì)應(yīng)APP的事件隊(duì)列->(3)APP從消息隊(duì)列頭取出事件->(4)交由Main Window進(jìn)行消息分發(fā)->(5)找到合適的Responder進(jìn)行處理,如果沒找到,則會(huì)沿著Responder chain返回到APP層,丟棄不響應(yīng)該事件
用戶觸發(fā)事件, IOKit.framework 生成一個(gè) IOHIDEvent 事件并由 SpringBoard 接收,SpringBoard會(huì)利用mach port,產(chǎn)生source1,來喚醒目標(biāo)APP的com.apple.uikit.eventfetch-thread的RunLoop。Eventfetch thread會(huì)將main runloop 中__handleEventQueue所對(duì)應(yīng)的source0設(shè)置為signalled == Yes狀態(tài),同時(shí)喚醒main RunLoop。mainRunLoop則調(diào)用__handleEventQueue進(jìn)行事件隊(duì)列處理。
dispatch_async(dispatch_get_main_queue(), block)
如當(dāng)調(diào)用了 dispatch_async(dispatch_get_main_queue(), block)時(shí),主隊(duì)列會(huì)把該 block 放到對(duì)應(yīng)的線程(恰好是主線程)中,主線程的 RunLoop 會(huì)被喚醒,從消息中取得這個(gè) block,回調(diào) CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 來執(zhí)行這個(gè) block
線程?;?/h4>
子線程默認(rèn)是完成任務(wù)后結(jié)束。當(dāng)要經(jīng)常使用子線程,每次開啟子線程比較耗性能。此時(shí)可以開啟子線程的 RunLoop,保持 RunLoop 運(yùn)行,則使子線程保持不死。AFNetworking 基于 NSURLConnection 時(shí)正是這樣做的,希望在后臺(tái)線程能保持活著,從而能接收到 delegate 的回調(diào)。
/* 返回一個(gè)線程 */
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
// 創(chuàng)建一個(gè)線程,并在該線程上執(zhí)行下一個(gè)方法
_networkRequestThread = [[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
// 開啟線程
[_networkRequestThread start];
});
return _networkRequestThread;
}
/* 在新開的線程中執(zhí)行的第一個(gè)方法 */
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
// 獲取當(dāng)前線程對(duì)應(yīng)的 RunLoop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 為 RunLoop 添加 source,模式為 DefaultMode
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
// 開始運(yùn)行 RunLoop
[runLoop run];
/ /或者
//[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
}
}
監(jiān)聽卡頓
核心思路:主線程runloop中添加監(jiān)聽,監(jiān)聽runloop狀態(tài)變化。如果runloop長(zhǎng)期處于kCFRunLoopBeforeSources(處理source0)或者kCFRunLoopAfterWaiting(處理source1),就說明出現(xiàn)了卡頓。
自定義線程間通信
