RunLoop 原理和核心機制

搞iOS以後一直沒有深刻研究過RunLoop,很是的慚愧。恰好前一陣子負責性能優化項目,須要利用RunLoop作性能優化和性能檢測,趁着這個機會深刻研究了RunLoop的原理和特性。編程

RunLoop的定義

當有持續的異步任務需求時,咱們會建立一個獨立的生命週期可控的線程。RunLoop就是控制線程生命週期並接收事件進行處理的機制。安全

RunLoop是iOS事件響應與任務處理最核心的機制,它貫穿iOS整個系統。性能優化

Foundation: NSRunLoop
Core Foundation: CFRunLoop 核心部分,代碼開源,C 語言編寫,跨平臺網絡

目的

經過RunLoop機制實現省電,流暢,響應速度快,用戶體驗好多線程

理解

進程是一家工廠,線程是一個流水線,Run Loop就是流水線上的主管;當工廠接到商家的訂單分配給這個流水線時,Run Loop就啓動這個流水線,讓流水線動起來,生產產品;當產品生產完畢時,Run Loop就會暫時停下流水線,節約資源。
RunLoop管理流水線,流水線纔不會由於無所事事被工廠銷燬;而不須要流水線時,就會辭退RunLoop這個主管,即退出線程,把全部資源釋放。架構

RunLoop並非iOS平臺的專屬概念,在任何平臺的多線程編程中,爲控制線程的生命週期,接收處理異步消息都須要相似RunLoop的循環機制實現,Android的Looper就是相似的機制。框架

特性

  • 主線程的RunLoop在應用啓動的時候就會自動建立
  • 其餘線程則須要在該線程下本身啓動
  • 不能本身建立RunLoop
  • RunLoop並非線程安全的,因此須要避免在其餘線程上調用當前線程的RunLoop
  • RunLoop負責管理autorelease pools
  • RunLoop負責處理消息事件,即輸入源事件和計時器事件

RunLoop機制

主線程 (有 RunLoop 的線程) 幾乎全部函數都從如下六個之一的函數調起:異步

  • CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
    CFRunloop is calling out to an abserver callback function
    用於向外部報告 RunLoop 當前狀態的更改,框架中不少機制都由 RunLoopObserver 觸發,如 CAAnimation函數

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
    CFRunloop is calling out to a block
    消息通知、非延遲的perform、dispatch調用、block回調、KVOoop

  • CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
    CFRunloop is servicing the main desipatch queue

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
    CFRunloop is calling out to a timer callback function
    延遲的perform, 延遲dispatch調用

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
    CFRunloop is calling out to a source 0 perform function
    處理App內部事件、App本身負責管理(觸發),如UIEvent、CFSocket。普通函數調用,系統調用

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
    CFRunloop is calling out to a source 1 perform function
    由RunLoop和內核管理,Mach port驅動,如CFMachPort、CFMessagePort

  • RunLoop 架構



  • RunLoop 運行時

主要有如下六種狀態:

  • kCFRunLoopEntry -- 進入runloop循環
  • kCFRunLoopBeforeTimers -- 處理定時調用前回調
  • kCFRunLoopBeforeSources -- 處理input sources的事件
  • kCFRunLoopBeforeWaiting -- runloop睡眠前調用
  • kCFRunLoopAfterWaiting -- runloop喚醒後調用
  • kCFRunLoopExit -- 退出runloop

RunLoop 運行時調用棧

  • 主線程App運行時

  • RunLoopObserver與Autorelease Pool的關係

UIKit 經過 RunLoopObserver 在 RunLoop 兩次 Sleep 間對 Autorelease Pool 進行 Pop 和 Push 將此次 Loop 中產生的 Autorelease 對象釋放。

  • RunLoop的掛起與喚醒

指定用於喚醒的 mach_port 端口
調用 mach_msg 監聽喚醒端口,被喚醒前系統內核將這個線程掛起,停留在mach_msg_trap狀態。
由另外一個線程向內核發送這個端口的msg後,trap狀態被喚醒,RunLoop繼續工做。

RunLoop支持的消息事件(Events)

  • RunLoop

圖片名稱

  • 支持接收處理輸入源(Input Source)事件,包括:

    系統的Mach Port事件,是一種通信事件
    自定義輸入事件

  • 支持接受處理定時源(Timer)事件

  • 在啓動RunLoop以前,必須添加監聽的輸入源事件或者定時源事件,不然調用[runloop run]會直接返回,而不會進入循環讓線程長駐。

    若是沒有添加任何輸入源事件或Timer事件,線程會一直在無限循環空轉中,會一直佔用CPU時間片,沒有實現資源的合理分配。
    沒有while循環且沒有添加任何輸入源或Timer的線程,線程會直接完成,被系統回收。

//錯誤作法 
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
while (!self.isCancelled && !self.isFinished) {
    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
};

//正確作法
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!self.isCancelled && !self.isFinished) {
    @autoreleasepool {
        [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
    }
}

Run Loop Modes

  • 理解
    Run Loop Mode就是流水線上支持生產的產品類型,流水線在一個時刻只能在一種模式下運行,生產某一類型的產品。消息事件就是訂單。

  • Cocoa定義了四中Mode

    Default:NSDefaultRunLoopMode,默認模式,在Run Loop沒有指定Mode的時候,默認就跑在Default Mode下
    Connection:NSConnectionReplyMode,用來監聽處理網絡請求NSConnection的事件
    Modal:NSModalPanelRunLoopMode,OS X的Modal面板事件
    Event tracking:UITrackingRunLoopMode,拖動事件
    Common mode:NSRunLoopCommonModes,是一個模式集合,當綁定一個事件源到這個模式集合的時候就至關於綁定到了集合內的每個模式

  • RunLoop能夠經過[acceptInputForMode:beforeDate:]和[runMode:beforeDate:]來指定在一段時間內的運行模式。若是不指定的話,RunLoop默認會運行在Default下(不斷重複調用runMode:NSDefaultRunLoopMode beforDate:)

  • 在主線程啓動一個計時器Timer,而後拖動UITableView或者UIScrollView,計時器不執行。這是由於,爲了更好的用戶體驗,在主線程中Event tracking模式的優先級最高。在用戶拖動控件時,主線程的Run Loop是運行在Event tracking Mode下,而建立的Timer是默認關聯爲Default Mode,所以系統不會當即執行Default Mode下接收的事件。解決方法:

NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                                   target:self
                                                 selector:@selector(timerFireMethod:)
                                                 userInfo:nil
                                                  repeats:YES];
                                                  
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 
//或 
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

[timer fire];

Run Loop應用實踐

Run Loop主要有如下三個應用場景:

  • 維護線程的生命週期,讓線程不自動退出,isFinished爲Yes時退出。
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!self.isCancelled && !self.isFinished) {
    @autoreleasepool {
            [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
    }
}
  • 建立常駐線程,執行一些會一直存在的任務。該線程的生命週期跟App相同
@autoreleasepool {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
}
  • 在必定時間內監聽某種事件,或執行某種任務的線程
    以下代碼,在30分鐘內,每隔30s執行onTimerFired:。這種場景通常會出如今,如我須要在應用啓動以後,在必定時間內持續更新某項數據。
@autoreleasepool {
    NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
    NSTimer * udpateTimer = [NSTimer timerWithTimeInterval:30
                                                    target:self
                                                  selector:@selector(onTimerFired:)
                                                  userInfo:nil
                                                   repeats:YES];
    [runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes];
    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60*30]];
}
  • AFNetworking中RunLoop的建立
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
         // 這裏主要是監聽某個 port,目的是讓這個 Thread 不會回收
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; 
        [runLoop run];
    }
}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread =
        [[NSThread alloc] initWithTarget:self
                                selector:@selector(networkRequestThreadEntryPoint:)
                                  object:nil];
        [_networkRequestThread start];
    });
    return _networkRequestThread;
}
相關文章
相關標籤/搜索