Runloop

weixin_33751566發表於2015-08-28

RunLoop

RunLoop就是執行迴圈,處理app中的各種事件(比如觸控事件,定時器事件,Selector事件)
一個執行緒對應一個RunLoop,主執行緒的RunLoop已經自動建立好了,子執行緒的RunLoop需要主動建立,RunLoop在第一次獲取時建立,執行緒結束時銷燬

RunLoop的獲得

  • Foundation
    • [NSRunLoop currentRunLoop]; // 獲得當前執行緒的RunLoop物件
    • [NSRunLoop mainRunLoop]; // 獲得主執行緒的RunLoop物件
  • Core Foundation
    • CFRunLoopGetCurrent(); // 獲得當前執行緒的RunLoop物件
    • CFRunLoopGetMain(); // 獲得主執行緒的RunLoop物件

RunLoop相關類

  • Core Foundation中關於RunLoop的5個類
    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef

RunLoop的組成

  • Mode
    • Timer
    • source
    • Observer

Mode

Mode代表RunLoop的執行模式。每次RunLoop職能指定一個Mode執行。切換Mode職能退出Loop,再重新指定一個Mode進入。
一個 RunLoop 包含若干個 Mode,每個Mode又包含若干個Source/Timer/Observer

系統預設的5個Mode

  • NSDefaultRunLoopMode:App的預設Mode,通常主執行緒是在這個Mode下執行

  • UITrackingRunLoopMode:介面跟蹤 Mode,用於 ScrollView 追蹤觸控滑動,保證介面滑動時不受其他 Mode 影響

  • UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成後就不再使用

  • GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到

  • NSRunLoopCommonModes: 這是一個佔位用的Mode,不是一種真正的Mode(既執行Default中的事件也執行track中的事件)

Timer

CFRunLoopTimerRef是基於時間的觸發器

CFRunLoopTimerRef基本上說的就是NSTimer,它受RunLoop的Mode影響

GCD的定時器不受RunLoop的Mode影響

- (void)timer
{
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    // 定時器只執行在NSDefaultRunLoopMode下,一旦RunLoop進入其他模式,這個定時器就不會工作(比如textField的拖動)
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
    // 定時器只執行在UITrackingRunLoopMode下,一旦RunLoop進入其他模式,這個定時器就不會工作
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    // 定時器會跑在標記為common modes的模式下
    // 標記為common modes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

- (void)timer2
{
    // 呼叫了scheduledTimer返回的定時器,已經自動被新增到當前runLoop中,而且是NSDefaultRunLoopMode
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    
    // 修改模式
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

- (void)run
{
    NSLog(@"----run");
}

Source

  • CFRunLoopSourceRef是事件源(輸入源)

  • 按照官方文件,Source的分類

    • Port-Based Sources (跟其他執行緒互動,或通過核心傳送的訊息)
    • Custom Input Sources (自動以輸入源,幾乎不用)
    • Cocoa Perform Selector Sources (比如performSelecter等方法)
  • 按照函式呼叫棧,Source的分類

    • Source0:非基於Port的
    • Source1:基於Port的,通過核心和其他執行緒通訊,接收、分發系統事件

事件的傳入都是先傳入Source1。然後再由Source1分發至source0進行處理。所以在函式呼叫棧中往往只看到Source0。

Obersver

CFRunLoopObserverRef是觀察者,能夠監聽RunLoop的狀態改變

  • 可以監聽的時間點
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), // 1
    kCFRunLoopBeforeTimers = (1UL << 1), // 2
    kCFRunLoopBeforeSources = (1UL << 2), // 4
    kCFRunLoopBeforeWaiting = (1UL << 5), // 32
    kCFRunLoopAfterWaiting = (1UL << 6), // 64
    kCFRunLoopExit = (1UL << 7), // 128
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
  • 新增Observer
- (void)observer
{
    // 建立observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----監聽到RunLoop狀態發生改變---%zd", activity);
    });

    // 新增觀察者:監聽RunLoop的狀態
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
    // 釋放Observer
    //凡是帶有Create、Copy、Retain等字眼的函式,建立出來的物件,都需要在最後做一次release
    //比如CFRunLoopObserverCreate
    //release函式:CFRelease(物件);
    
    CFRelease(observer);
}

RunLoop處理邏輯

  • 1:通知觀察者run loop已經啟動
  • 2:通知觀察者任何即將要開始的定時器
  • 3:通知觀察者任何即將啟動的source0
  • 4:啟動任何準備好的source0
  • 5:如果接收到source1準備好並處於等待狀態,立即啟動,並進入步驟9
  • 6:通知觀察者執行緒進入休眠
  • 7:將執行緒置於休眠直到任何一下面的事件發生:
    • 某一事件到達source1
    • 定時器啟動
    • runloop設定的時間超時
    • runloop被顯式喚醒
  • 8:通知觀察者執行緒將被喚醒
  • 9:處理未處理的事件
    • 如果使用者定義的定時器啟動,處理定時器事件並重啟runloop,進入步驟2
    • 如果輸入源啟動,傳遞相應的訊息
    • 如果runloop被顯示喚醒而且時間還未超時,重啟runloop,進入步驟2
  • 10:通知觀察者runloop結束