RunLoop學習總結

什么是RunLoop

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

基本作用

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

RunLoop對象

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

獲得RunLoop對象

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

RunLoop與線程

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

  • 主線程的RunLoop已經(jīng)自動創(chuàng)建和啟動,子線程的RunLoop需要主動創(chuàng)建、調(diào)用run方法啟動

  • RunLoop在第一次獲取時創(chuàng)建,在線程結束時銷毀

RunLoop資料

Core Foundation中關于RunLoop的5個類

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoopModeRef

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

  • 關系

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

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

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

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

    • GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到

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

"CommonModes"概念

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

  • 應用場景

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

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

  • 定時器NSTimer,代碼實現(xiàn)

    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];
// 調(diào)用了scheduledTimer返回的定時器,已經(jīng)自動被添加到當前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 是事件產(chǎn)生的地方。Source有兩個版本:Source0 和 Source1。
  • Source0 非基于Port的
    ,只包含了一個回調(diào)(函數(shù)指針),它并不能主動觸發(fā)事件。使用時,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標記為待處理,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。
  • 基于Port的,通過內(nèi)核和其他線程通信,接收、分發(fā)系統(tǒng)事件

CFRunLoopTimerRef

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

CFRunLoopObserverRef

  • CFRunLoopObserverRef是觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變
  • 可以監(jiān)聽的時間點有以下幾個
/* 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 // 可以監(jiān)聽以上所有狀態(tài)
};
  • 添加觀察者Observer代碼實現(xiàn)
 // 創(chuàng)建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----監(jiān)聽到RunLoop狀態(tài)發(fā)生改變---%zd", activity);
    });

    // 添加觀察者:監(jiān)聽RunLoop的狀態(tài)
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

    // 釋放Observer
    CFRelease(observer);

CF的內(nèi)存管理(Core Foundation)

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

PerformSelecter

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

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

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

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

使用RunLoop開啟一個常駐線程

  • 讓一個子線程不進入消亡狀態(tài),等待其他線程發(fā)來消息,處理其他事件

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

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

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

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

RunLoop處理邏輯

  • 官方版


    RunLoop1.png
  • 網(wǎng)友版

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

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

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

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