RunLoop的定義
當(dāng)有持續(xù)的異步任務(wù)需求時,我們會創(chuàng)建一個獨立的生命周期可控的線程。RunLoop就是控制線程生命周期并接收事件進(jìn)行處理的機制。
RunLoop是iOS事件響應(yīng)與任務(wù)處理最核心的機制,它貫穿iOS整個系統(tǒng)。
Foundation: NSRunLoop
Core Foundation: CFRunLoop 核心部分,代碼開源,C 語言編寫,跨平臺
理解
進(jìn)程是一家工廠,線程是一個流水線,Run Loop就是流水線上的主管;當(dāng)工廠接到商家的訂單分配給這個流水線時,Run Loop就啟動這個流水線,讓流水線動起來,生產(chǎn)產(chǎn)品;當(dāng)產(chǎn)品生產(chǎn)完畢時,Run Loop就會暫時停下流水線,節(jié)約資源。
RunLoop管理流水線,流水線才不會因為無所事事被工廠銷毀;而不需要流水線時,就會辭退RunLoop這個主管,即退出線程,把所有資源釋放。
RunLoop并不是iOS平臺的專屬概念,在任何平臺的多線程編程中,為控制線程的生命周期,接收處理異步消息都需要類似RunLoop的循環(huán)機制實現(xiàn),Android的Looper就是類似的機制。
特性
- 主線程的RunLoop在應(yīng)用啟動的時候就會自動創(chuàng)建
- 其他線程則需要在該線程下自己啟動
- 不能自己創(chuàng)建RunLoop
- RunLoop并不是線程安全的,所以需要避免在其他線程上調(diào)用當(dāng)前線程的RunLoop
- RunLoop負(fù)責(zé)管理autorelease pools
- RunLoop負(fù)責(zé)處理消息事件,即輸入源事件和計時器事件
RunLoop機制
- 支持接收處理輸入源(Input Source)事件,包括:
系統(tǒng)的Mach Port事件,是一種通訊事件
自定義輸入事件
- 支持接受處理定時源(Timer)事件
在啟動RunLoop之前,必須添加監(jiān)聽的輸入源事件或者定時源事件,否則調(diào)用[runloop run]會直接返回,而不會進(jìn)入循環(huán)讓線程長駐。
如果沒有添加任何輸入源事件或Timer事件,線程會一直在無限循環(huán)空轉(zhuǎn)中,會一直占用CPU時間片,沒有實現(xiàn)資源的合理分配。
沒有while循環(huán)且沒有添加任何輸入源或Timer的線程,線程會直接完成,被系統(tǒng)回收。
//錯誤做法
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.isFinished)
{
@autoreleasepool { [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]]; }
}
Run Loop Modes
- 理解
Run Loop Mode就是流水線上支持生產(chǎn)的產(chǎn)品類型,流水線在一個時刻只能在一種模式下運行,生產(chǎn)某一類型的產(chǎn)品。消息事件就是訂單。
Cocoa定義了四中Mode
Default:NSDefaultRunLoopMode,
默認(rèn)模式,在Run Loop沒有指定Mode的時候,默認(rèn)就跑在Default Mode下
Connection:NSConnectionReplyMode,
用來監(jiān)聽處理網(wǎng)絡(luò)請求NSConnection的事件
Modal:NSModalPanelRunLoopMode,
OS X的Modal面板事件
Event tracking:UITrackingRunLoopMode,拖動事件
Common mode:NSRunLoopCommonModes,是一個模式集合,當(dāng)綁定一個事件源到這個模式集合的時候就相當(dāng)于綁定到了集合內(nèi)的每一個模式
RunLoop可以通過[acceptInputForMode:beforeDate:]和[runMode:beforeDate:]來指定在一段時間內(nèi)的運行模式。如果不指定的話,RunLoop默認(rèn)會運行在Default下(不斷重復(fù)調(diào)用runMode:NSDefaultRunLoopMode beforDate:)
在主線程啟動一個計時器Timer,然后拖動UITableView或者UIScrollView,計時器不執(zhí)行。這是因為,為了更好的用戶體驗,在主線程中Event tracking模式的優(yōu)先級最高。在用戶拖動控件時,主線程的Run Loop是運行在Event tracking Mode下,而創(chuàng)建的Timer是默認(rèn)關(guān)聯(lián)為Default Mode,因此系統(tǒng)不會立即執(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)用實踐
Run Loop主要有以下三個應(yīng)用場景:
- 維護(hù)線程的生命周期,讓線程不自動退出,isFinished為Yes時退出。
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!self.isCancelled && !self.isFinished)
{ @autoreleasepool { [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]]; }
}
- 創(chuàng)建常駐線程,執(zhí)行一些會一直存在的任務(wù)。該線程的生命周期跟App相同
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
- 在一定時間內(nèi)監(jiān)聽某種事件,或執(zhí)行某種任務(wù)的線程
如下代碼,在30分鐘內(nèi),每隔30s執(zhí)行onTimerFired:。這種場景一般會出現(xiàn)在,如我需要在應(yīng)用啟動之后,在一定時間內(nèi)持續(xù)更新某項數(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]];}
- AFNetworking中RunLoop的創(chuàng)建
+ (void)networkRequestThreadEntryPoint:(id)__unused object
{
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 這里主要是監(jiān)聽某個 port,目的是讓這個 Thread 不會回收
[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;
}