RunLoop學習總結

什么是RunLoop

  • 從字面上看,就是運行循環,跑圈
  • 其實它內部就是do-while循環,在這個循環內部不斷地處理各種任務(比如Source、Timer、Observer)
  • 循環體的開始需要檢測是否有需要處理的事件,如果有則去處理,如果沒有則進入睡眠以節省CPU時間

基本作用

  1. 保持程序的持續運行
  2. 處理App中的各種事件(比如觸摸事件、定時器事件、Selector事件)
  3. 節省CPU資源,提高程序性能:該做事時做事,該休息時休息
  4. ······

RunLoop對象

  • iOS中有2套API來訪問和使用RunLoop
    • Core Foundation
    • NSRunLoop
  • NSRunLoop和CFRunLoopRef都代表著RunLoop對象
  • Core Foundation
    • CFRunLoopRef是在 CoreFoundation 框架內的,它提供了純 C 函數的 API,所有這些 API 都是線程安全的。
  • NSRunLoop是基于CFRunLoopRef的一層OC包裝,提供了面向對象的 API,但是這些 API 不是線程安全的。所以要了解RunLoop內部結構,需要多研究CFRunLoopRef層面的API(Core Foundation層面)

獲得RunLoop對象

  • Foundation
[NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
  • Core Foundation
CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象

RunLoop與線程

  • 每條線程都有唯一的一個與之對應的RunLoop對象,其關系是保存在一個全局的 Dictionary 里

  • 主線程的RunLoop已經自動創建和啟動,子線程的RunLoop需要主動創建、調用run方法啟動

  • RunLoop在第一次獲取時創建,在線程結束時銷毀

RunLoop資料

Core Foundation中關于RunLoop的5個類

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoopModeRef

  • CFRunLoopModeRef 類并沒有對外暴露,只是通過 CFRunLoopRef的接口進行了封裝,代表RunLoop的運行模式

  • 關系

    • 一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer
    • 每次調用 RunLoop 的主函數時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode
    • 如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進入
    • 這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響
  • 系統默認注冊了5個Mode:

    • NSDefaultRunLoopMode:App的默認Mode,通常主線程是在這個Mode下運行

    • UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響

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

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

    • NSRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode

"CommonModes"概念

  • 一個 Mode 可以將自己標記為"Common"屬性(通過將其 ModeName 添加到 RunLoop 的 "commonModes" 中)。每當 RunLoop 的內容發生變化時,RunLoop 都會自動將 _commonModeItems 里的 Source/Observer/Timer 同步到具有 "Common" 標記的所有Mode里。

  • 應用場景

    • 主線程的 RunLoop 里有兩個預置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。這兩個 Mode 都已經被標記為"Common"屬性。DefaultMode 是 App 平時所處的狀態,TrackingRunLoopMode 是追蹤 ScrollView 滑動時的狀態。當你創建一個 Timer 并加到 DefaultMode 時,Timer 會得到重復回調,但此時滑動一個TableView時,RunLoop 會將 mode 切換為 TrackingRunLoopMode,這時 Timer 就不會被回調,并且也不會影響到滑動操作。

    • 有時你需要一個 Timer,在兩個 Mode 中都能得到回調,一種辦法就是將這個 Timer 分別加入這兩個 Mode。還有一種方式,就是將 Timer 加入到頂層的 RunLoop 的 "commonModeItems" 中。"commonModeItems" 被 RunLoop 自動更新到所有具有"Common"屬性的 Mode 里去

  • 定時器NSTimer,代碼實現

    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    // 定時器只運行在NSDefaultRunLoopMode下,一旦RunLoop進入其他模式,這個定時器就不會工作
    [[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];
// 調用了scheduledTimer返回的定時器,已經自動被添加到當前runLoop中,而且是NSDefaultRunLoopMode
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    // 修改模式
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

CFRunLoopSourceRef

  • CFRunLoopSourceRef 是事件產生的地方。Source有兩個版本:Source0 和 Source1。
  • Source0 非基于Port的
    ,只包含了一個回調(函數指針),它并不能主動觸發事件。使用時,你需要先調用 CFRunLoopSourceSignal(source),將這個 Source 標記為待處理,然后手動調用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。
  • 基于Port的,通過內核和其他線程通信,接收、分發系統事件

CFRunLoopTimerRef

  • CFRunLoopTimerRef是基于時間的觸發器
  • CFRunLoopTimerRef基本上說的就是NSTimer,它受RunLoop的Mode影響
  • GCD的定時器不受RunLoop的Mode影響

CFRunLoopObserverRef

  • CFRunLoopObserverRef是觀察者,能夠監聽RunLoop的狀態改變
  • 可以監聽的時間點有以下幾個
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), // 1 // 即將進入Loop
    kCFRunLoopBeforeTimers = (1UL << 1), // 2 // 即將處理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 4 即將處理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 32 // 即將進入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), // 64
    // 剛從休眠中喚醒
    kCFRunLoopExit = (1UL << 7), // 128 // 即將退出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU // 可以監聽以上所有狀態
};
  • 添加觀察者Observer代碼實現
 // 創建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----監聽到RunLoop狀態發生改變---%zd", activity);
    });

    // 添加觀察者:監聽RunLoop的狀態
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

    // 釋放Observer
    CFRelease(observer);

CF的內存管理(Core Foundation)

  • 凡是帶有Create、Copy、Retain等字眼的函數,創建出來的對象,都需要在最后做一次release
    • 比如CFRunLoopObserverCreate
  • release函數:CFRelease(對象);

PerformSelecter

  • 當調用 NSObject 的 performSelecter:afterDelay: 后,實際上其內部會創建一個 Timer 并添加到當前線程的 RunLoop 中。所以如果當前線程沒有 RunLoop,則這個方法會失效。

  • 當調用 performSelector:onThread: 時,實際上其會創建一個 Timer 加到對應的線程去,同樣的,如果對應線程沒有 RunLoop 該方法也會失效。

  • 應用:可以讓某些事件(行為、任務)在特定模式下執行

    • 有時候圖片比較大,渲染到屏幕耗費時間,會造成界面卡頓,可以讓圖片在UIScrollView滾動完之后執行
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:2 inModes:@[NSDefaultRunLoopMode]];

使用RunLoop開啟一個常駐線程

  • 讓一個子線程不進入消亡狀態,等待其他線程發來消息,處理其他事件

  • 子線程RunLoop的三種手動啟動方式

    // 默認RunLoop指定的模式是NSDefaultRunLoopMode,周期無限長
    [[NSRunLoop currentRunLoop] run];

    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];

    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
  • 常駐線程,讓子線程不消亡,兩種實現方式
    • 給RunLoop的Mode添加一個Source或者Timer
    // RunLoop只能選擇一個Mode啟動,如果當前Mode中沒有任何Source、Timer,那么就直接退出RunLoop
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    
    [[NSRunLoop currentRunLoop] run];
    
    • 一直循環啟動RunLoop,退出RunLoop,直到添加了Source或Timer,才會跑圈
    while (1) {
        [[NSRunLoop currentRunLoop] run];
    }
    

RunLoop處理邏輯

  • 官方版


    RunLoop1.png
  • 網友版

RunLoop2.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Run loop 剖析:Runloop 接收的輸入事件來自兩種不同的源:輸入源(intput source)和定時...
    Mitchell閱讀 12,465評論 17 111
  • 什么是Runloop · 一般來講,一個線程一次只能執行一個任務,執行完成后線程就會退出。如果我們需要一個機制,讓...
    806349745123閱讀 309評論 0 1
  • 通過閱讀YY大神的博客深入理解RunLoop還有觀看了孫源大大@sunnyxx錄制的RunLoop視頻,總算對Ru...
    巫師學徒閱讀 325評論 0 0
  • 一、什么是runloop 字面意思是“消息循環、運行循環”。它不是線程,但它和線程息息相關。一般來講,一個線程一次...
    WeiHing閱讀 8,168評論 11 111
  • Runloop是iOS和OSX開發中非常基礎的一個概念,從概念開始學習。 RunLoop的概念 -般說,一個線程一...
    小貓仔閱讀 1,019評論 0 1