RunLoop的理解

今天同事問我RunLoop的知識。說他平時會用但是有些地方還是不清楚,所以我特意整理了一下,因為網(wǎng)上關于RunLoop的講解很多,這里我寫的是個人對RunLoop的理解,希望寫的易懂一些讓更多的人明白。

RunLoop基本作用

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

程序一開始系統(tǒng)就為主線程創(chuàng)建了一個RunLoop,這也是一個app能一直運行的原因。

下面兩句話很重要

  1. 每條線程都有唯一的一個與之對應的RunLoop對象
  2. 主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要主動創(chuàng)建

不管是主線程還是子線程都可以有個RunLoop 而且只能有一個。主線程的RunLoop在程序開始的時候已經(jīng)創(chuàng)建好了,子線程的RunLoop需要我們自己去創(chuàng)建。

獲取RunLoop的方式

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

蘋果不允許直接創(chuàng)建 RunLoop,它只提供了兩個自動獲取的函數(shù):NSRunLoop currentRunLoop( CFRunLoopGetMain( ) )和 NSRunLoop mainRunLoop( CFRunLoopGetCurrent( ) )。這兩個函數(shù)內部的邏輯大概是下面這樣:

/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 訪問 loopsDic 時的鎖
static CFSpinLock_t loopsLock;

/// 獲取一個 pthread 對應的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);

if (!loopsDic) {
    // 第一次進入時,初始化全局Dic,并先為主線程創(chuàng)建一個 RunLoop。
    loopsDic = CFDictionaryCreateMutable();
    CFRunLoopRef mainLoop = _CFRunLoopCreate();
    CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}

/// 直接從 Dictionary 里獲取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));

if (!loop) {
    /// 取不到時,創(chuàng)建一個
    loop = _CFRunLoopCreate();
    CFDictionarySetValue(loopsDic, thread, loop);
    /// 注冊一個回調,當線程銷毀時,順便也銷毀其對應的 RunLoop。
    _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}

OSSpinLockUnLock(&loopsLock);
return loop;
}

以上代碼來源網(wǎng)絡

子線程剛創(chuàng)建時并沒有 RunLoop,如果你不主動獲取,那它一直都不會有。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時,RunLoop 的銷毀是發(fā)生在線程結束時。

CFRunLoopModeRefbo(Mode)

RunLoop的Mode有五種:

  1. kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
  2. **UITrackingRunLoopMode **:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
  3. **UIInitializationRunLoopMode **: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用(系統(tǒng)用)
  4. **GSEventReceiveRunLoopMode **: 接受系統(tǒng)事件的內部 Mode,通常用不到(系統(tǒng)用)
  5. **kCFRunLoopCommonModes **: 這是一個占位用的Mode,不是一種真正的Mode(用來占位)

ps:其實kCFRunLoopCommonModes標記了UITrackingRunLoopMode和kCFRunLoopDefaultMode兩種模式,這里大家先了解一下后面會講到。

這張圖特別好,我就拿來用了

看上圖,這張圖描述的很清楚一個RunLoop可以包含多個Mode。

請務必記住下面的幾句話?。。。。?/h3>
  • CFRunLoopModeRef代表RunLoop的運行模式

  • 一個 RunLoop 包含若干個 Mode,每個Mode又包含若干個Source/Timer/Observer

  • 每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode

  • 如果需要切換Mode,只能退出Loop,再重新指定一個Mode進入

  • 這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響

下面我們來舉個例子

[NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> target:<#(nonnull id)#> selector:<#(nonnull SEL)#> userInfo:<#(nullable id)#> repeats:<#(BOOL)#>];

這個方法是創(chuàng)建一個NSTimer,并把它加入到RunLoopDefaultMode 模式下的RunLoop中。

NSTimer *time=[NSTimer timerWithTimeInterval:<#(NSTimeInterval)#> target:<#(nonnull id)#> selector:<#(nonnull SEL)#> userInfo:<#(nullable id)#> repeats:<#(BOOL)#>]

而這個方法只是創(chuàng)建了一個NSTimer

我們可以把這個timer加到一個RunLoop中

[[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];

這樣也可以實現(xiàn)上面代碼實現(xiàn)的目的:創(chuàng)建一個NSTimer,并把它加入到RunLoopDefaultMode 模式下的RunLoop中。

現(xiàn)在有這樣一個場景,添加一個定時器并把它加入到NSDefaultRunLoopMode,定時器正常運行,這時屏幕開始滑動,你會發(fā)現(xiàn)你的定時器不管用了,這個為什么呢?
這是因為在進行滑動的時候RunLoop進入了UITrackingRunLoopMode(忘記的請查看RunLoop的Mode)而你的time是在:NSDefaultRunLoopMode模式下的所以失效了當手一松開又進入了NSDefaultRunLoopMode,定時器又可以工作了現(xiàn)在該怎么辦呢?
接下來我們按住NSDefaultRunLoopMode進入頭文件看看發(fā)現(xiàn)

FOUNDATION_EXPORT NSString * const NSDefaultRunLoopMode;
FOUNDATION_EXPORT NSString * const NSRunLoopCommonModes NS_AVAILABLE(10_5, 2_0);

里面怎么就兩個Mode,而且有一個還是用來占位的,別急我們打印一下在NSRunLoopCommonModes的RunLoop看看它的Mode是什么

 **common modes = <CFBasicHash 0x7f8009e035e0 [0x1095c5a40]>{type = mutable set, count = 2,**
 **entries =>**
 ** 0 : <CFString 0x10a4fc210 [0x1095c5a40]>{contents = "UITrackingRunLoopMode"}**
 ** 2 : <CFString 0x1095e65e0 [0x1095c5a40]>{contents = "kCFRunLoopDefaultMode"}**
 **}**

注意看**common modes **包含UITrackingRunLoopMode和kCFRunLoopDefaultMode
所以當你有一個定時器,能讓它在屏幕滑動的情況下和正常情況下都還能使用就應該使用NSRunLoopCommonModes。
如果只讓定時器在屏幕滑動時刻起作用該怎么辦呢?
加入到UITrackingRunLoopMode模式下不就行了,哈哈。

學到這里大家應該知道一個RunLoop可以有多個Mode,但是在每次RunLoop啟動時,只能指定其中一個 Mode

這張圖特別好,我就拿來用了

如果感覺這篇文章對您有所幫助,順手點個喜歡,謝謝啦

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

相關閱讀更多精彩內容

  • 什么情況下使用runloop? runloop好比就是跑圈,就是一個線程一直在做某一件事情。 一般主線程會自動運行...
    進擊的小杰閱讀 4,539評論 4 7
  • Run loop 剖析:Runloop 接收的輸入事件來自兩種不同的源:輸入源(intput source)和定時...
    Mitchell閱讀 12,645評論 17 111
  • 每個線程有一個消息循環(huán) —> 消息循環(huán)監(jiān)聽著輸入事件—> 事件有兩種類型 輸入源和定時源 —> 將創(chuàng)建好的輸入源以...
    金歌漫舞閱讀 413評論 0 0
  • RunLoop的概念 一般來說,一個線程一次只能執(zhí)行一個任務,執(zhí)行完成線程就會推出。如果我們需要一個機制,讓線程能...
    無神閱讀 166評論 0 2
  • 留胡須的男人,我天生有些抗拒,對這些男人沒好感。 也許有些女人很喜歡,覺得留胡須的男人很有型,很帥,覺得很有男人味...
    碼字好玩兒閱讀 275評論 0 0

友情鏈接更多精彩內容