RunLoop

概述

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)系是如下圖所示


runloop的組成
runloop的組成

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事件。


RunLoop事件源

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)行原理

image.png

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)了卡頓。

自定義線程間通信

自定義線程間通信
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容