iOS RunLoop 探究

RunLoop常見用法

AFN
AFN2.x中把網絡請求所有都放在一個子線程中進行。因爲子線程運行完任務後就會自動銷燬,因此在子線程中運行了一個Runloop保證線程不會被銷燬掉。(線程的建立和銷燬耗費的資源雖然不多,可是大量網絡請求致使大量建立和銷燬所耗費的資源仍是十分可觀的)javascript

#pragma mark AFN
+ (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;
}

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}複製代碼

用CFRunloop也可創建一個Runloopjava

CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
    //A perform callback for the run loop source. This callback is called when the source has fired.
    //Availability
    context.perform = fire;

    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);

    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    NSLog(@"自定義RunLoopRun");
    CFRunLoopRun();複製代碼

線程與RunLoop的一些概念

線程安全
  • CFRunLoopRef 是線程安全的
  • NSRunLoop 非線程安全
線程運行
  • 子線程執行完任務自動銷燬
  • runloop實際是一個循環,並不會自動中止
  • 添加runloop後,run,若是要銷燬這個線程,必需要中止runloop。
  • 若是當前線程沒有Runloop就

關於AFN 3.x

  • 因爲NSUrlSession參考了AF的2.x的優勢,本身維護了一個線程池,作Request線程的調度與管理,因此在AF3.x中,沒有了常駐線程,都是用的時候run,結束的時候stop。

線程間的通訊

  • iOS線程間的通訊,其實是各類輸入源,觸發Runloop去處理對應的事件。

在什麼狀況下使用RunLoop

僅當在爲你的程序建立輔助線程的時候,你才須要顯式運行一個run loop。
Run loop在你要和線程有更多的交互時才須要,好比如下狀況:
>編程

  • 使用端口或自定義輸入源來和其餘線程通訊
  • 使用線程的定時器
  • Cocoa中使用任何performSelector…的方法
  • 使線程週期性工做

RunLoop 詳細介紹


Run Loop的處理兩大類事件源:Timer Source和Input Source(包括performSelector* 方法簇、Port或者自定義Input Source),每一個事件源都會綁定在Run Loop的某個特定模式mode上,並且只有RunLoop在這個模式運行的時候纔會觸發該Timer和Input Source。xcode

  • Runloop 處理兩大類事件源 1.Timer Source 2.Input Source
  • 若是沒有任何事件源添加到Run Loop上,Run Loop就會馬上exit
Input Source:傳遞異步事件,一般消息來源於其餘線程或程序。
  • 基於端口的輸入源
    • Cocoa和Cocoa Foundation 內置支持使用端口相關的對象和函數來建立的機遇端口的源。
    • Cocoa中只要簡單的建立端口對象,將端口添加到Runloop便可。端口對象會本身處理建立和配置輸入源。
    • 在Core Fundation中,你必須人工建立端口和他的Runloop源。咱們可使用端口相關的函數(CFMachPortRef,CFMessagePortRef,CFSocketRef)來建立合適的對象。

Example:安全

void createPortSource()
{

    CFMessagePortRef port = CFMessagePortCreateLocal(kCFAllocatorDefault, CFSTR("com.someport"),myCallbackFunc, NULL, NULL);
    CFRunLoopSourceRef source =  CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, port, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
    while (pageStillLoading) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        CFRunLoopRun();
        [pool release];
    }

    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
}複製代碼
  • 自定義輸入源
    • 自定義輸入源須要人工從其餘線程發送。
    • 使用Core Fundation中的CFRunLoopSourceRef類型相關的函數來建立。
    • 須要定義消息傳遞機制
      Example:
      void createCustomSource()
      {
      CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
      CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
      CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
      while (pageStillLoading) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        CFRunLoopRun();
        [pool release];
      }
      CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
      CFRelease(source);
      }複製代碼
  • Cocoa的Selector源網絡

    • 除了基於端口的源,Cocoa定義了自定義輸入源,容許你在任何線程執行selector方法
    • 當在其餘線程上面執行selector時,目標線程須有一個活動的run loop。對於你建立的線程,這意味着線程在你顯式的啓動run loop以前是不會執行selector方法的,而是一直處於休眠狀態。(致使Crash
    • 和基於端口的源同樣。執行selector請求會在目標線程上序列化,減緩多線程上容許多個方法容易引發的同步問題。
  • 定時源多線程

    • 定時源在預設的時間點同步方式傳遞消息,這些消息都會發生在特定時間或者重複的時間間隔。定時源直接傳遞消息給處理例程,不會當即退出run loop.
    • 定時器與runloop中的特定模式有關。
      Example:
      //方法一:
      NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:4.0
                                                     target:self
                                                   selector:@selector(backgroundThreadFire:) userInfo:nil
                                                    repeats:YES];
      [[NSRunLoop currentRunLoop] addTimer:timerforMode:NSDefaultRunLoopMode];
      //方法二:
      [NSTimer scheduledTimerWithTimeInterval:10
                                        target:self
                                       selector:@selector(backgroundThreadFire:)
                                       userInfo:nil
                                       repeats:YES];複製代碼

事件源按照函數調用棧的分類

  • 基於端口的輸入源
    • 在runloop中,被定義名爲souce1。Cocoa和Core Foundation內置支持使用端口相關的對象和函數來建立的基於端口的源。例如,在Cocoa裏面你歷來不須要直接建立輸入源。你只要簡單的建立端口對象,並使用NSPort的方法把該端口添加到run loop。端口對象會本身處理建立和配置輸入源。
  • 非基於port,自定義輸入源
RunLoop 觀察者

源是在合適的同步或異步事件發生時觸發,而run loop觀察者則是在run loop自己運行的特定時候觸發。你可使用run loop觀察者來爲處理某一特定事件或是進入休眠的線程作準備。你能夠將run loop觀察者和如下事件關聯:異步

  1. Runloop入口
  2. Runloop什麼時候處理一個定時器
  3. Runloop什麼時候處理一個輸入源
  4. Runloop什麼時候進入睡眠狀態
  5. Runloop什麼時候被喚醒,但在喚醒以前要處理的事件
  6. Runloop終止
RunLoop事件隊列

每次運行run loop,你線程的run loop對會自動處理以前未處理的消息,並通知相關的觀察者。具體的順序以下:函數

  1. 通知觀察者run loop已經啓動
  2. 通知觀察者任何即將要開始的定時器
  3. 通知觀察者任何即將啓動的非基於端口的源
  4. 啓動任何準備好的非基於端口的源
  5. 若是基於端口的源準備好並處於等待狀態,當即啓動;並進入步驟9。
  6. 通知觀察者線程進入休眠
  7. 將線程置於休眠直到任一下面的事件發生:
    • 某一事件到達基於端口的源
    • 定時器啓動
    • Run loop設置的時間已經超時
    • run loop被顯式喚醒
  8. 通知觀察者線程將被喚醒。
  9. 處理未處理的事件
    • 若是用戶定義的定時器啓動,處理定時器事件並重啓run loop。進入步驟2
    • 若是輸入源啓動,傳遞相應的消息
    • 若是run loop被顯式喚醒並且時間還沒超時,重啓run loop。進入步驟2
  10. 通知觀察者run loop結束。

由於定時器和輸入源的觀察者是在相應的事件發生以前傳遞消息,因此通知的時間和實際事件發生的時間之間可能存在偏差。若是須要精確時間控制,你可使用休眠和喚醒通知來幫助你校對實際發生事件的時間。oop

由於當你運行run loop時定時器和其它週期性事件常常須要被傳遞,撤銷run loop也會終止消息傳遞。典型的例子就是鼠標路徑追蹤。由於你的代碼直接獲取到消息而不是經由程序傳遞,所以活躍的定時器不會開始直到鼠標追蹤結束並將控制權交給程序。

Run loop能夠由run loop對象顯式喚醒。其它消息也能夠喚醒run loop。例如,添加新的非基於端口的源會喚醒run loop從而能夠當即處理輸入源而不須要等待其餘事件發生後再處理。

從這個事件隊列中能夠看出:

①若是是事件到達,消息會被傳遞給相應的處理程序來處理, runloop處理完當次事件後,run loop會退出,而無論以前預約的時間到了沒有。你能夠從新啓動run loop來等待下一事件。

②若是線程中有須要處理的源,可是響應的事件沒有到來的時候,線程就會休眠等待相應事件的發生。這就是爲何run loop能夠作到讓線程有工做的時候忙於工做,而沒工做的時候處於休眠狀態。

何時使用run loop

僅當在爲你的程序建立輔助線程的時候,你才須要顯式運行一個run loop。Run loop是程序主線程基礎設施的關鍵部分。因此,Cocoa和Carbon程序提供了代碼運行主程序的循環並自動啓動run loop。IOS程序中UIApplication的run方法(或Mac OS X中的NSApplication)做爲程序啓動步驟的一部分,它在程序正常啓動的時候就會啓動程序的主循環。相似的,RunApplicationEventLoop函數爲Carbon程序啓動主循環。若是你使用xcode提供的模板建立你的程序,那你永遠不須要本身去顯式的調用這些例程。

對於輔助線程,你須要判斷一個run loop是不是必須的。若是是必須的,那麼你要本身配置並啓動它。你不須要在任何狀況下都去啓動一個線程的run loop。好比,你使用線程來處理一個預先定義的長時間運行的任務時,你應該避免啓動run loop。
若是你決定在程序中使用run loop,那麼它的配置和啓動都很簡單。和全部線程編程同樣,你須要計劃好在輔助線程退出線程的情形。讓線程天然退出每每比強制關閉它更好。

CFRunLoop 對外接口

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoopSourceRef

  • Source0 只包含了一個回調(函數指針),它並不能主動觸發事件。使用時,你須要先調用CFRunLoopSourceSignal(source),將這個Source標記爲待處理時間,而後手動調用CFRunLoopWeakUp(runloop)來喚醒RunLoop,讓其處理這個事件。
  • Source1 包含了一個mach_port和一個回調(函數指針),被用於經過內核和其餘線程相互發送消息。這種Source能主動喚醒RunLoop的線程。

CFRunLoopTimerRef 是基於時間的觸發器
CFRunLoopObserverRef 是觀察者

Runloop 使用

Run Loop運行接口

  • 要操做Run Loop,Foundation層和Core Foundation層都有對應的接口能夠操做Run Loop:
    Foundation層對應的是NSRunLoop,Core Foundation層對應的是CFRunLoopRef;
    兩組接口差很少,不過功能上仍是有許多區別的:

  • 例如CF層能夠添加自定義Input Source事件源、(CFRunLoopSourceRef)Run Loop觀察者Observer(CFRunLoopObserverRef),不少相似功能的接口特性也是不同的。
    NSRunLoop的運行接口:

//運行 NSRunLoop,運行模式爲默認的NSDefaultRunLoopMode模式,沒有超時限制
- (void)run;
//運行 NSRunLoop: 參數爲運時間期限,運行模式爲默認的NSDefaultRunLoopMode模式 
- (void)runUntilDate:(NSDate *)limitDate;
//運行 NSRunLoop: 參數爲運行模式、時間期限,返回值爲YES表示是處理事件後返回的,NO表示是超時或者中止運行致使返回的
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;複製代碼
模式問題

Run Loop運行時只能以一種固定的模式運行,若是咱們須要它切換模式,只有停掉它,再從新開啓它。運行時它只會監控這個模式下添加的Timer Source和Input Source,若是這個模式下沒有相應的事件源,Run Loop的運行也會馬上返回的。注意Run Loop不能在運行在NSRunLoopCommonModes模式,由於NSRunLoopCommonModes實際上是個模式集合,而不是一個具體的模式,我能夠在添加事件源的時候使用NSRunLoopCommonModes,只要Run Loop運行在NSRunLoopCommonModes中任何一個模式,這個事件源均可以被觸發。

相關文章
相關標籤/搜索