簡單談?wù)凴unLoop

 * author:conowen@大鐘                                                                                                                          
 * E-mail:conowen@hotmail.com      

1、RunLoop定義

從字面上看,run是運(yùn)行,執(zhí)行的意思,loop是循環(huán)的意思,其實(shí)RunLoop就是運(yùn)行循環(huán)的意思。一般來說,一個線程一次只能執(zhí)行一個任務(wù),執(zhí)行完了,線程就跑完退出。那么下一個新的任務(wù)來,我們還需要重新創(chuàng)建和配置一個線程,這樣的話,就比較消耗性能。而RunLoop就可以讓線程一直在循環(huán)跑,而不退出。RunLoop的內(nèi)部其實(shí)就是一個do while循環(huán)(除非你手動傳入退出RunLoop的事件),但是它并不是像普通的while循環(huán)一樣,讓CPU一直在工作,一直在檢測新的事件。 當(dāng)有新的事件進(jìn)來的時候,RunLoop就通知線程去工作,當(dāng)沒有新的事件的時候,RunLoop就會進(jìn)入休眠狀態(tài),節(jié)約系統(tǒng)資源。

例如主線程,應(yīng)用啟動的時候,默認(rèn)就啟動了主線程的RunLoop,一直在監(jiān)聽用戶的屏幕觸摸時間,鍵盤輸入事件等,當(dāng)應(yīng)用被殺掉的時候,主線程結(jié)束,RunLoop就被銷毀了。

附上CFRunLoop源代碼CFRunLoop.c

void CFRunLoopRun(void) {   /* DOES CALLOUT */  
    int32_t result;  
    do {//就是一個do while循環(huán)  
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);  
        CHECK_FOR_FORK();  
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);  
}  
  
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */  
    CHECK_FOR_FORK();  
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);  
}  
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */  
    CHECK_FOR_FORK();  
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;  
    __CFRunLoopLock(rl);  
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);  
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {  
    Boolean did = false;  
    if (currentMode) __CFRunLoopModeUnlock(currentMode);  
    __CFRunLoopUnlock(rl);  
    return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;  
    }  
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);  
    CFRunLoopModeRef previousMode = rl->_currentMode;  
    rl->_currentMode = currentMode;  
    int32_t result = kCFRunLoopRunFinished;  
  
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);  
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);  
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);  
  
        __CFRunLoopModeUnlock(currentMode);  
        __CFRunLoopPopPerRunData(rl, previousPerRun);  
    rl->_currentMode = previousMode;  
    __CFRunLoopUnlock(rl);  
    return result;  
}  

2、Runloop與線程

首先,每個線程都有一個綁定的RunLoop對象,包括主線程。RunLoop不需要用戶創(chuàng)建,你可以通過函數(shù)來獲取當(dāng)前的Runloop,當(dāng)你第一次獲取當(dāng)前線程RunLoop的時候,就會被創(chuàng)建,而RunLoop是隨著線程的結(jié)束就銷毀。

可以理解為Runloop是來管理線程的

3:runloop在第一次獲取時被創(chuàng)建,在線程結(jié)束時被銷毀。

iOS中有兩套API來訪問和使用RunLoop

  • Foundation框架:NSRunLoop
  • Core Foundation框架:CFRunLoopRef

NSRunLoop和CFRunLoopRef都代表著RunLoop對象,NSRunLoop是基于CFRunLoopRef的一層OC封裝而已。

//獲得RunLoop對象  
//Foundation NSRunLoop  
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對象  
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象  
  
  
//Core Foundation <span style="font-family: SimSun;">NSRunLoop</span>  
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對象  
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象  

非主線程的RunLoop不會自動運(yùn)行,你必須在傳入驅(qū)動事件才能啟動RunLoop。

Runloop事件源與Observer

RunLoop有兩種事件源:

  • input sources

Source0
需要手動喚醒線程:添加Source0到RunLoop并不會主動喚醒線程,需要手動喚醒)① 觸摸事件處理② performSelector:onThread:

Source1
具備喚醒線程的能力① 基于 Port 的線程間通信② 系統(tǒng)事件捕捉:系統(tǒng)事件捕捉是由Source1來處理,然后再交給Source0處理

  • Timer sources
    1>Timer Source 定時器事件

RunLoop在處理上述了這兩種sources時,會產(chǎn)生通知,通知內(nèi)容包含RunLoop的狀態(tài)信息,我們可以在線程中注冊RunLoop Observer來監(jiān)聽Runloop的行為與狀態(tài)變化,然后根據(jù)這些通知,做出相對應(yīng)的操作。

具體來說,注冊一個RunLoop Observer可以監(jiān)聽RunLoop以下幾種狀態(tài)變化。

/* Run Loop Observer Activities */  
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {  
    kCFRunLoopEntry = (1UL << 0),  // 進(jìn)入runloop
    kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理timer
    kCFRunLoopBeforeSources = (1UL << 2),// 即將處理source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), // 從休眠狀態(tài)被喚醒, 但是  還沒開始處理事件
    kCFRunLoopExit = (1UL << 7), // 退出runloop
};  

有個技巧,通過回調(diào)runloop的生命周期函數(shù),計(jì)算kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting之間的時間差,可以知道當(dāng)前l(fā)oop是否卡頓,做一些卡頓判斷。如果在這兩個生命周期之間3次超過50ms,那可能就發(fā)生了卡頓。

3、RunLoop的Mode

RunLoop的Mode其實(shí)就是input sources 、Timer sources和RunLoop Observer三者的集合。一個Runloop包含若干個Mode,一個Mode包含著若干個source和Observer,當(dāng)你RunLoop在運(yùn)行的時候,你必須(而且只能)給它指定一個特定的Mode(CurrentMode)。當(dāng)要變換Mode的時候,只能退出當(dāng)前l(fā)oop(退出當(dāng)前event循環(huán)),重新指定的Mode再運(yùn)行。

簡單來說,runloop本質(zhì)是消息循環(huán)處理的一套機(jī)制,它的每次loop的其實(shí)處理一次Mode的過程,Mode包含input sources 、Timer sources和RunLoop Observer三者的集合,每次loop只能處理一次Mode。

以下是五種Mode類型。

Default NSDefaultRunLoopMode kCFRunLoopDefaultMode(Core Foundation) The default mode is the one used for most operations. Most of the time, you should use this mode to start your run loop and configure your input sources. Default模式,一般情況下應(yīng)使用此Mode。主線程RunLoop啟動的就是默認(rèn)模式
Connection NSConnectionReplyMode(Cocoa) Cocoa uses this mode in conjunction with NSConnection objects to monitor replies. You should rarely need to use this mode yourself. 使用這個Mode來監(jiān)控NSConnection對象的回復(fù),用戶基本不會使用此Mode。
Modal NSModalPanelRunLoopMode(Cocoa) Cocoa uses this mode to identify events intended for modal panels. 使用這個Mode來處理modal panels的事件
Event tracking NSEventTrackingRunLoopMode(Cocoa) Cocoa uses this mode to restrict incoming events during mouse-dragging loops and other sorts of user interface tracking loops. 處理用戶的一些拖動事件,如UIScrollView的滾動
Common modes NSRunLoopCommonModes(Cocoa) kCFRunLoopCommonModes(Core Foundation) This is a configurable group of commonly used modes. Associating an input source with this mode also associates it with each of the modes in the group. For Cocoa applications, this set includes the default, modal, and event tracking modes by default. Core Foundation includes just the default mode initially. You can add custom modes to the set using theCFRunLoopAddCommonMode funtion.事實(shí)上這并不是一個Mode,這只是有關(guān)聯(lián)關(guān)系的“Mode”,像一個“標(biāo)識”,如果設(shè)置RunLoop的Mode為這個模式,就可以把Sources同時添加到RunLoop的 default、modal、event tracking模式中(下面用一個場景解釋這個到底有啥用)
獲取當(dāng)前線程的RunLoop Mode  
NSString* runLoopMode = [[NSRunLoop currentRunLoop] currentMode];

4、界面滾動的時候?yàn)槭裁碞STimer會失效

因?yàn)橹骶€程的RunLoop是跑在Default模式的,在主線程中創(chuàng)建一個NSTimer,并制定task,當(dāng)UIScrollView滾動的時候,主線程的RunLoop就切換到Event Tracking Mode,然而NSTimer默認(rèn)是添加到Default Mode的,滾動的時候,RunLoop只能執(zhí)行一個Mode,所以NSTimer的 Mode就退出了。

要解決這種問題,以下有三種方法:

  • a、把NSTimer添加到另外一個線程中去(非主線程),然后開始運(yùn)行這個線程的RunLoop。

  • b、把NSTimer所在的RunLoop Mode改為Event Tracking模式

[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]  
//直接把NSTimer添加到Event Tracking模式中  
  • c、把NSTimer所在的RunLoop Mode改為Common模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];  
//設(shè)置Common Modes,把NSTimer同時添加到defaul、modal、Event Tracking模式中  

因?yàn)镽unLoop一次只能執(zhí)行一個Mode(包含各種Sources),所以把NStimer添加到與滾動事件所在的Mode就行,或者重新開一個線程(不同的RunLoop)。

參考文章

https://hit-alibaba.github.io/interview/iOS/ObjC-Basic/Runloop.html

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

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

  • Runloop 是和線程緊密相關(guān)的一個基礎(chǔ)組件,是很多線程有關(guān)功能的幕后功臣。盡管在平常使用中幾乎不太會直接用到,...
    jackyshan閱讀 10,008評論 10 75
  • 先貼下 apple doc, 本文基本是對照 doc 的翻譯:https://developer.apple.co...
    brownfeng閱讀 7,113評論 8 111
  • 說明iOS中的RunLoop使用場景1.保持線程的存活,而不是線性的執(zhí)行完任務(wù)就退出了<1>不開啟RunLoop的...
    野生塔塔醬閱讀 6,918評論 15 109
  • 以問答的形式介紹以下內(nèi)容:從線程的角度理解 RunLoop,RunLoop Mode 的設(shè)計(jì)機(jī)制及使用技巧,以 R...
    seedante閱讀 2,842評論 1 21
  • 概述 RunLoop作為iOS中一個基礎(chǔ)組件和線程有著千絲萬縷的關(guān)系,同時也是很多常見技術(shù)的幕后功臣。盡管在平時多...
    陽明AI閱讀 1,151評論 0 17

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