RunLoop 原理及核心機(jī)制
RunLoop 原理及核心機(jī)制
從iOS開始就沒(méi)有對(duì)RunLoop進(jìn)行過(guò)深入的研究,非常慚愧。碰巧前陣子負(fù)責(zé)性能優(yōu)化項(xiàng)目,需要利用RunLoop進(jìn)行性能優(yōu)化和性能測(cè)試,借此機(jī)會(huì)對(duì)RunLoop的原理和特點(diǎn)進(jìn)行了深入的研究。
定義RunLoop
如果需要持續(xù)的異步任務(wù),我們將創(chuàng)造一個(gè)獨(dú)立的生命周期可控的過(guò)程。RunLoop是一種控制線程生命周期并接受事件處理的機(jī)制。
RunLoop是iOS事件響應(yīng)和任務(wù)處理的核心機(jī)制,它貫穿整個(gè)iOS系統(tǒng)。
Foundation: NSRunLoop
Core Foundation: CFRunLoop 核心部分,代碼開源,C 語(yǔ)言寫作,跨平臺(tái)
目的
通過(guò)RunLoop機(jī)制實(shí)現(xiàn)節(jié)能、平穩(wěn)、快速響應(yīng)、良好的客戶體驗(yàn)。
理解
過(guò)程是一家工廠,過(guò)程是一條流水線,Run 在流水線上,Loop就是主管;當(dāng)工廠收到商家的訂單分配給這條流水線時(shí),Run Loop開始了這條流水線,讓流水線移動(dòng),進(jìn)行生產(chǎn);當(dāng)產(chǎn)品完成后,Run Loop將暫時(shí)停止流水線,節(jié)約能源。
RunLoop管理流水線,流水線不會(huì)因?yàn)闊o(wú)所事事而被工廠銷毀;而且不需要流水線的時(shí)候,RunLoop這個(gè)主管就會(huì)被解雇,也就是退出流程,釋放全部資源。
RunLoop并不是iOS平臺(tái)的專屬概念,在任何平臺(tái)的多線程編程中,Android的Looper都需要類似RunLoop的循環(huán)機(jī)制來(lái)實(shí)現(xiàn),以控制線程的生命周期。
特點(diǎn)
- 當(dāng)使用啟動(dòng)時(shí),主線程的RunLoop將自動(dòng)建立
- 另外一個(gè)過(guò)程需要在這個(gè)過(guò)程下自行啟動(dòng)。
- 無(wú)法建立自己的RunLoop
- RunLoop不是線程安全的,所以需要避免將RunLoop調(diào)用到其他過(guò)程中。
- 負(fù)責(zé)管理autoreleaseasease的RunLoop pools
- RunLoop負(fù)責(zé)處理消息事件,即輸入源事件和記時(shí)器事件
RunLoop機(jī)制
主線程 (有 RunLoop 的進(jìn)程) 幾乎所有函數(shù)都是從以下六個(gè)函數(shù)中調(diào)整起來(lái)的:
- CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
CFRunloop is calling out to an abserver callback function
用來(lái)向外界報(bào)告 RunLoop 當(dāng)前狀態(tài)的變化,框架中的許多機(jī)制都是由 RunLoopObserver 觸發(fā),如 CAAnimation - CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
CFRunloop is calling out to a block
消息通知,perform,非延遲、調(diào)用dispatch,回調(diào)block,KVO - CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
CFRunloop is servicing the main desipatch queue - CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
CFRunloop is calling out to a timer callback function
perform延遲, 延遲dispatch調(diào)用 - CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0__PERFORM_FUNCTION
CFRunloop is calling out to a source 0 perform function
處理App內(nèi)部事件,App本身負(fù)責(zé)管理(觸發(fā)),例如UIEvent。、CFSocket。通用函數(shù)調(diào)用,系統(tǒng)調(diào)用 - CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
CFRunloop is calling out to a source 1 perform function
由RunLoop和核心管理,Mach CFMachPort驅(qū)動(dòng)程序、CFMessagePort - RunLoop 架構(gòu)
??
- RunLoop 運(yùn)行時(shí)
以下六種狀態(tài)主要有:
- kCFRunLoopEntry -- 進(jìn)入runloop循環(huán)循環(huán)循環(huán)
- kCFRunLoopBeforeTimers -- 定期調(diào)用前回調(diào)處理
- kCFRunLoopBeforeSources -- 處理input sources事件
- kCFRunLoopBeforeWaiting -- 睡前調(diào)用runloop
- kCFRunLoopAfterWaiting -- 喚起后調(diào)用runloop
- kCFRunLoopExit -- 退出runloop
RunLoop 運(yùn)轉(zhuǎn)時(shí)調(diào)用棧
- 運(yùn)行主線程App時(shí)
- ObserverRunLoopAutorelease Pool的關(guān)系
UIKit 通過(guò) RunLoopObserver 在 RunLoop 兩次 Sleep 間對(duì) Autorelease Pool 進(jìn)行 Pop 和 Push 將這次 Loop 中產(chǎn)生的 Autorelease 目標(biāo)釋放。
- 掛起和喚起RunLoop
mach端口_port
調(diào)用mach_msg監(jiān)控喚起端口,喚起前系統(tǒng)內(nèi)核將此線程掛起,停留在mach_msg_trap狀態(tài)。
這個(gè)端口的msg由另一個(gè)過(guò)程發(fā)送到核心后,trap狀態(tài)被喚起,RunLoop繼續(xù)工作。
消息事件支持RunLoop(Events)
- RunLoop
- 支持輸入源的接收處理(Input Source)包括:
Mach系統(tǒng) Port事件,是一種通信事件
定制輸入事件
- 對(duì)處理定時(shí)源的支持(Timer)事件
- 啟動(dòng)RunLoop前,必須添加監(jiān)控輸入源事件或定時(shí)源事件,否則調(diào)用。[runloop run]將直接返回,而不進(jìn)入循環(huán)使進(jìn)程長(zhǎng)駐。
如果沒(méi)有添加任何輸入源事件或Timer事件,過(guò)程將繼續(xù)循環(huán)和空轉(zhuǎn),CPU時(shí)間片將永遠(yuǎn)占用,資源的合理分配將無(wú)法實(shí)現(xiàn)。
沒(méi)有while循環(huán),也沒(méi)有添加任何輸入源或Timer的過(guò)程,過(guò)程將直接完成,并被系統(tǒng)回收。
//錯(cuò)誤的做法
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
while (!self.isCancelled && !self.isFinished) {
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
};
//正確的做法
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!self.isCancelled && !self.isCancelled && !self.isFinished) {
@autoreleasepool {
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
}
}
Run Loop Modes
- 理解
Run Loop Mode是流水線上支持生產(chǎn)的產(chǎn)品類型。流水線只能在一定時(shí)間內(nèi)以一種模式運(yùn)行,生產(chǎn)某種類型的產(chǎn)品。新聞事件是訂單。 - Cocoa定義了四中Mode
Default:NSDefaultRunLoopMode,Run是默認(rèn)的 當(dāng)Loop沒(méi)有指定Mode時(shí),默認(rèn)情況下,它會(huì)在Default上運(yùn)行。 Mode下
Connection:NSConnectionReplyMode,對(duì)NSConnection事件進(jìn)行監(jiān)控處理。
Modal:NSModalPanelRunLoopMode,OS Modal面板事件X
Event tracking:UITrackingRunLoopMode,拖動(dòng)事件
Common mode:NSRunLoopCommonModes,這是一種集合方式。當(dāng)一個(gè)事件來(lái)源被綁定到這個(gè)模式集合時(shí),它相當(dāng)于綁定到集合中的每一種方式。
- RunLoop可以通過(guò)[acceptInputForMode:beforeDate:]和[runMode:beforeDate:]指定一段時(shí)間內(nèi)的運(yùn)行模式。如果沒(méi)有指定,RunLoop默認(rèn)會(huì)在Default下運(yùn)行(runModede反復(fù)調(diào)用):NSDefaultRunLoopMode beforDate:)
- 啟動(dòng)主線程中的記時(shí)器Timer,然后拖動(dòng)UITableView或UIScrollView,記時(shí)器不會(huì)執(zhí)行。因?yàn)?,為了更好的客戶體驗(yàn),Event在主線程中。 優(yōu)先考慮tracking模式??蛻敉蟿?dòng)控件時(shí),主線程的Run 在Event中運(yùn)行Loop tracking 在Mode下,建立的Timer默認(rèn)與Defaultt相關(guān)聯(lián)。 Mode,所以系統(tǒng)不會(huì)立即執(zhí)行Default 接受Mode下的事件。解決方法:
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerFireMethod:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//或
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
[timer fire];
Run Loop應(yīng)用實(shí)踐
Run Loop主要有以下三個(gè)應(yīng)用領(lǐng)域:
- 維持過(guò)程的生命周期,使過(guò)程不會(huì)自動(dòng)退出,isFinished在Yes時(shí)退出。
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!self.isCancelled && !self.isFinished) {
@autoreleasepool {
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
}
}
- 建立一個(gè)長(zhǎng)期的停留過(guò)程,執(zhí)行一些將永遠(yuǎn)存在的任務(wù)。這個(gè)過(guò)程的生命周期和應(yīng)用程序一樣。
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
- 監(jiān)控某一事件,或者在一定時(shí)間內(nèi)執(zhí)行某一任務(wù)的過(guò)程。
下面的代碼,在30分鐘內(nèi),每30秒執(zhí)行onTimerFired。:。這樣的情況一般都會(huì)出現(xiàn),比如我需要在使用之后,在一定的時(shí)間內(nèi)不斷更新某些數(shù)據(jù)。
@autoreleasepool {
NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
NSTimer * udpateTimer = [NSTimer timerWithTimeInterval:30
target:self
selector:@selector(onTimerFired:)
userInfo:nil
repeats:YES];
[runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes];
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60*30]];
}
- RunLoop在AFNetworking中的建立
(void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 這兒主要是監(jiān)控一個(gè) port,目的就是讓這個(gè) Thread 不會(huì)回收
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
(NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread =
[[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
本文僅代表作者觀點(diǎn),版權(quán)歸原創(chuàng)者所有,如需轉(zhuǎn)載請(qǐng)?jiān)谖闹凶⒚鱽?lái)源及作者名字。
免責(zé)聲明:本文系轉(zhuǎn)載編輯文章,僅作分享之用。如分享內(nèi)容、圖片侵犯到您的版權(quán)或非授權(quán)發(fā)布,請(qǐng)及時(shí)與我們聯(lián)系進(jìn)行審核處理或刪除,您可以發(fā)送材料至郵箱:service@tojoy.com

