* 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