RunLoop
Runloop 是和線程緊密相關(guān)的一個(gè)基礎(chǔ)組件筹煮,是很多線程有關(guān)功能的幕后功臣。盡管在平常使用中幾乎不太會(huì)直接用到族扰,理解 Runloop 有利于我們更加深入地理解 iOS 的多線程模型大猛。
目的:保住當(dāng)前線程的生命,監(jiān)聽事件:觸摸、時(shí)鐘薪缆、網(wǎng)絡(luò)等
RunLoop基本概念
RunLoop是什么?顧名思義伞广,說(shuō)白了就是一種循環(huán)拣帽,只不過(guò)它這種循環(huán)比較高級(jí)。一般的while循環(huán)會(huì)導(dǎo)致CPU進(jìn)入忙等待狀態(tài)嚼锄,而Runloop則是一種“閑”等待减拭,這部分可以類比Linux下的epoll。當(dāng)沒(méi)事件時(shí)区丑,Runloop會(huì)進(jìn)入休眠狀態(tài)峡谊,有事件發(fā)生時(shí),Runloop會(huì)去找對(duì)應(yīng)的Handler處理事件刊苍。Runloop可以讓線程在需要做事的時(shí)候忙起來(lái),不需要的話就讓線程休眠濒析。
圖中展示了Runloop在線程中的作用:從 input source 和 timer source 接受事件正什,然后在線程中處理事件。
Runloop 與線程
Runloop 和線程是綁定在一起的号杏。每個(gè)線程(包括主線程)都有一個(gè)對(duì)應(yīng)的Runloop對(duì)象婴氮。我們并不能自己創(chuàng)建Runloop對(duì)象斯棒,但是可以獲取到系統(tǒng)提供的Runloop對(duì)象。
主線程的 Runloop 會(huì)在應(yīng)用啟動(dòng)的時(shí)候完成啟動(dòng)主经,其他線程的 Runloop 默認(rèn)并不會(huì)啟動(dòng)荣暮,需要我們手動(dòng)啟動(dòng)。
Input Source 和 Timer Source
這兩個(gè)都是 Runloop 時(shí)間的來(lái)源罩驻,其中Input Source 又可以分為三類
- Port-Base Source穗酥,系統(tǒng)底層的Port時(shí)間,例如CFSocketRef惠遏,在應(yīng)用層基本用不到
- Custom Input Source砾跃,用戶手動(dòng)創(chuàng)建的Source
- Cocoa Perform Selector Source,Cacoa提供的的performSelector系列方法节吮,也是一種事件源
按照函數(shù)調(diào)用棧,Source可分為 Source0
和 Source1
抽高。其中 Source1
為系統(tǒng)內(nèi)核事件, Source0
即非Source1
例如:創(chuàng)建事件源
//隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
//創(chuàng)建一個(gè)定時(shí)器!!
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//設(shè)置定時(shí)器
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1000000000, 0);
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"-------%@",[NSThread currentThread]);
});
//啟動(dòng)定時(shí)器
dispatch_resume(self.timer);
Timer Source 顧名思義就是指定時(shí)器事件了。例如:NStimer
創(chuàng)建定時(shí)器
Runloop Observer
Runloop 通過(guò)監(jiān)控Source來(lái)決定有沒(méi)有任務(wù)要做透绩,除此之外翘骂,我們還可以用Runloop Observer 可以監(jiān)控下面的 runloop 事件:
- The entrance to the run loop.
- When the run loop is about to process a timer.
- When the run loop is about to process an input source.
- When the run loop is about to go to sleep.
- When the run loop has woken up, but before it has processed the event that woke it up.
- The exit from the run loop.
通過(guò)Runloop Observer 可以實(shí)現(xiàn)卡頓優(yōu)化,如:TableView滑動(dòng)中加載多張大圖
思路:每次Runloop循環(huán),只渲染一張大圖
步驟:
1.監(jiān)聽Runloop的循環(huán)
2.將加載大圖的代碼!放在一個(gè)數(shù)組里面
3.每次Runloop循環(huán),取出一個(gè)加載大圖的任務(wù)執(zhí)行
#pragma mark - <CFRunloop>
- (void)addTasks:(runloopBlock)task{
[self.tasks addObject:task];
if (self.tasks.count > 18) {
[self.tasks removeObjectAtIndex:0];
}
}
- (void)addRunloopObserver{
//獲取Runloop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
//定義一個(gè)context
CFRunLoopObserverContext context = {
0,
(__bridge void *)(self),
&CFRetain,
&CFRelease,
NULL
};
//定義觀察者
static CFRunLoopObserverRef runloopObserver;
runloopObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &callBack, &context);
//添加觀察者
CFRunLoopAddObserver(runloop, runloopObserver, kCFRunLoopCommonModes);
//C里面 一旦creat new copy
CFRelease(runloopObserver);
}
void callBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
ViewController * vc = (__bridge ViewController *)info;
if(vc.tasks.count == 0){
return;
}
runloopBlock block = vc.tasks.firstObject;
block();
[vc.tasks removeObjectAtIndex:0];
}
Runloop Mode
在監(jiān)視與被監(jiān)視中帚豪, Runloop 要處理的事情還挺復(fù)雜的碳竟。為了讓Runloop 能專心處理自己關(guān)心的那部分事情,引入了Runloop Mode 概念志鞍。
如圖所示瞭亮, Runloop Model 實(shí)際上是Source、Timer 和 Observer 的集合固棚,不同的Model 把不同組的 Source统翩、 Timer 和 Observer 隔絕開來(lái)。Runloop 在某個(gè)時(shí)刻只能跑在一個(gè) Mode 下此洲,處理這一個(gè) Model 種種的 Source厂汗,Timer 和 Observer。
蘋果文檔中提到的 Mode 有五個(gè)呜师,分別是:
- NSDefaultRunLoopMode
- NSConnectionReplyModel
- NSModalPanelRunLoopMode
- NSEventTrackingRunLoopMode
- NSRunLoopCommonModes
iOS中公開暴露出來(lái)的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonMode.NSRunLoopCommonMode實(shí)際上是一個(gè)Mode的集合娶桦,默認(rèn)包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode
與 RunLoop 相關(guān)的坑
日常開發(fā)中,與 runloop 接觸最近的可能就是NSTimerle汁汗。一個(gè) Timer一次只能加入到一個(gè) RunLoop中衷畦。我們?nèi)粘J褂玫臅r(shí)候,通常就是加入到當(dāng)前的runLoop的default model中知牌,而ScrollView 在用戶滑動(dòng)的時(shí)候祈争,主線程RunLoop會(huì)轉(zhuǎn)到NSTrackingRunLoopMode。er這個(gè)時(shí)候角寸,Timer就不會(huì)運(yùn)行菩混。
有如下兩種解決方案:
- 第一種:設(shè)置RunLoop Mode忿墅,例如NSTimer,我們指定它
運(yùn)行于NSRunLoopCommonModes沮峡,這是一個(gè)Mode的集
合疚脐。注冊(cè)到這個(gè)Mode下后,無(wú)論當(dāng)前runLoop運(yùn)行哪個(gè)
mode邢疙,時(shí)間都能得到執(zhí)行棍弄。 - 第二種:另一種解決Timer的方法是,我們?cè)诹硪粋€(gè)線程執(zhí)行和處理Timer事件秘症,然后在主線程更新UI照卦。