1.什么是RunLoop
- RunLoop 實(shí)際上是一個(gè)對(duì)象扯俱,這個(gè)對(duì)象在循環(huán)中用來(lái)處理程序運(yùn)行過(guò)程中出現(xiàn)的各種事件(比如說(shuō)觸摸事件喇澡、UI刷新事件、定時(shí)器事件呕屎、Selector事件)敬察,從而保持程序的持續(xù)運(yùn)行蹂安。
- NSRunLoop是iOS的消息處理模式,
- RunLoop是iOS里線程的一部分,任何線程,包括主線程都包含了一個(gè)Run Loop對(duì)象田盈。
- RunLoop的作用相當(dāng)于在線程上維持一個(gè)類(lèi)似while的死循環(huán)允瞧,在這個(gè)循環(huán)里內(nèi)部不斷的處理各種任務(wù)(比如:source/timer/Observer),并且在不執(zhí)行任務(wù)時(shí)痹升,RunLoop 會(huì)讓線程進(jìn)入睡眠狀態(tài)(不占用CPU資源)视卢。當(dāng)事件源發(fā)生時(shí)RunLoop會(huì)喚醒線程來(lái)處理事件
2.RunLoop和線程的關(guān)系
2.1 RunLoop和線程是一一對(duì)應(yīng)的据过。
2.2 RunLoop是來(lái)管理線程的绳锅,如果沒(méi)有RunLoop鳞芙,那么線程執(zhí)行完任務(wù)就會(huì)結(jié)束原朝。
2.3 線程銷(xiāo)毀前,會(huì)先釋放這個(gè)線程所對(duì)應(yīng)的RunLoop.
2.4 主線程的 RunLoop 對(duì)象系統(tǒng)自動(dòng)幫助我們創(chuàng)建好了喳坠,而子線程的 RunLoop對(duì)象需要我們主動(dòng)創(chuàng)建和維護(hù)。
3.主線程的 RunLoop 原理
在啟動(dòng)iOS程序的時(shí)候晾浴,系統(tǒng)會(huì)調(diào)用創(chuàng)建項(xiàng)目時(shí)自動(dòng)生成的 main.m 的文件脊凰。其中 UIApplicationMain 函數(shù)內(nèi)部幫我們開(kāi)啟了主線程的 RunLoop狸涌,UIApplicationMain 內(nèi)部擁有一個(gè)無(wú)限循環(huán)的代碼,只要程序不退出/崩潰仑性,它就一直循環(huán)歼捐。
4.RunLoop什么情況下使用
- 線程中使用ports 或 input sources 和其他線程通信
- 在線程中使用timers // 如果不啟動(dòng)runloop豹储,timer的事件是不會(huì)響應(yīng)的
- 在Cocoa 應(yīng)用中使用performSelector…方法 // 應(yīng)該是performSelector…這種方法會(huì)啟動(dòng)一個(gè)線程并啟動(dòng)run loop
- 讓線程執(zhí)行一個(gè)周期性的任務(wù)
- 可以控制定時(shí)器在特定模式下執(zhí)行
- 可以讓某些事件(行為巩剖、任務(wù))在特定模式下執(zhí)行
- 可以添加Observer監(jiān)聽(tīng)RunLoop的狀態(tài)钠怯,比如監(jiān)聽(tīng)點(diǎn)擊事件的處理(在所有點(diǎn)擊事件之前做一些事情)
5.RunLoop 相關(guān)類(lèi)
Run Loop Mode是一個(gè)集合佳魔,包括監(jiān)聽(tīng):事件源,定時(shí)器晦炊,以及需通知的runloop observers鞠鲜,Core Foundation框架下關(guān)于 RunLoop 有 5 個(gè)類(lèi):
- CFRunLoopRef:代表 RunLoop 的對(duì)象
- CFRunLoopModeRef:代表 RunLoop 的運(yùn)行模式
- CFRunLoopSourceRef:就是 RunLoop 模型圖中提到的輸入源 / 事件源
- CFRunLoopTimerRef:就是 RunLoop 模型圖中提到的定時(shí)源
- CFRunLoopObserverRef:觀察者,能夠監(jiān)聽(tīng) RunLoop 的狀態(tài)改變
5.1 相關(guān)類(lèi)之間的關(guān)系
一個(gè)RunLoop對(duì)象(CFRunLoopRef)中包含若干個(gè)運(yùn)行模式(CFRunLoopModeRef)断国。而每一個(gè)運(yùn)行模式下又包含若干個(gè)輸入源(CFRunLoopSourceRef)贤姆、定時(shí)源(CFRunLoopTimerRef)、觀察者(CFRunLoopObserverRef)稳衬。
每次 RunLoop 啟動(dòng)時(shí)霞捡,只能指定其中一個(gè)運(yùn)行模式(CFRunLoopModeRef),這個(gè)運(yùn)行模式(CFRunLoopModeRef)被稱(chēng)作當(dāng)前運(yùn)行模式(CurrentMode)。只有與這個(gè)mode關(guān)聯(lián)的事件源才會(huì)被監(jiān)聽(tīng)并被允許分發(fā)事件,同理,也只有與這個(gè)mode關(guān)聯(lián)的observer才會(huì)被通知,否則就處于暫停狀態(tài)叹阔。
如果需要切換運(yùn)行模式(CFRunLoopModeRef)睛藻,只能退出當(dāng)前 Loop,再重新指定一個(gè)運(yùn)行模式(CFRunLoopModeRef)進(jìn)入。這樣做主要是為了分隔開(kāi)不同組的輸入源(CFRunLoopSourceRef)、定時(shí)源(CFRunLoopTimerRef)、觀察者(CFRunLoopObserverRef)有滑,讓其互不影響 。
5.2 CFRunLoopRef 類(lèi)
CFRunLoopRef 是 Core Foundation 框架下 RunLoop 對(duì)象類(lèi),在Foundation 框架下 RunLoop 對(duì)象類(lèi)是NSRunLoop吼驶。
我們可通過(guò)以下方式來(lái)獲取 RunLoop 對(duì)象:
Core Foundation
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的 RunLoop 對(duì)象
CFRunLoopGetMain(); // 獲得主線程的 RunLoop 對(duì)象
Foundation
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的 RunLoop 對(duì)象
[NSRunLoop mainRunLoop]; // 獲得主線程的 RunLoop 對(duì)象
5.3 CFRunLoopModeRef類(lèi)
系統(tǒng)默認(rèn)定義了多種運(yùn)行模式(CFRunLoopModeRef),如下:
- kCFRunLoopDefaultMode:App的默認(rèn)運(yùn)行模式,通常主線程是在這個(gè)運(yùn)行模式下運(yùn)行
- UITrackingRunLoopMode:跟蹤用戶交互事件(例如 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他Mode影響)
- UIInitializationRunLoopMode:在剛啟動(dòng)App時(shí)第進(jìn)入的第一個(gè) Mode做盅,啟動(dòng)完成后就不再使用
- GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部事件图筹,通常用不到
- kCFRunLoopCommonModes:偽模式,不是一種真正的運(yùn)行模式
其中kCFRunLoopDefaultMode、UITrackingRunLoopMode蛔钙、kCFRunLoopCommonModes是我們開(kāi)發(fā)中需要用到的模式攻冷。
5.4 CFRunLoopTimerRef類(lèi)
CFRunLoopTimerRef是定時(shí)源(RunLoop模型圖中提到過(guò))禁谦,理解為基于時(shí)間的觸發(fā)器刽漂,基本上就是NSTimer颈畸。
- (void)viewDidLoad {
[super viewDidLoad];
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 將定時(shí)器添加到當(dāng)前RunLoop的NSDefaultRunLoopMode下
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
- (void)run{
NSLog(@"---run");
}
但當(dāng)頁(yè)面有像ScrollView试伙、’Text View滾動(dòng)時(shí)疏叨,run方法不打印了,當(dāng)松開(kāi)鼠標(biāo)的時(shí)候糊余,NSTimer就又開(kāi)始正常工作了尊沸。這是因?yàn)椋?br>
當(dāng)我們不做任何操作的時(shí)候壶熏,RunLoop處于NSDefaultRunLoopMode下蝌数。
而當(dāng)我們拖動(dòng)的時(shí)候饵撑,RunLoop就結(jié)束NSDefaultRunLoopMode,切換到了UITrackingRunLoopMode模式下唆貌,這個(gè)模式下沒(méi)有添加NSTimer滑潘,所以NSTimer就不工作。
但當(dāng)我們松開(kāi)鼠標(biāo)的時(shí)候锨咙,RunLoop就結(jié)束UITrackingRunLoopMode模式众羡,又切換回NSDefaultRunLoopMode模式,所以NSTimer就又開(kāi)始正常工作蓖租。
將上述代碼改為
[[NSRunLoop currentRunLoop] addTimer:timer forMode:kCFRunLoopCommonModes];
偽模式(kCFRunLoopCommonModes)粱侣,這其實(shí)不是一種真實(shí)的模式,而是一種標(biāo)記模式蓖宦,意思就是可以在打上Common Modes(NSDefaultRunLoopMode 和 UITrackingRunLoopMode齐婴。)標(biāo)記的模式下運(yùn)行。
5.5 CFRunLoopSourceRef類(lèi)
CFRunLoopSourceRef是事件源稠茂,CFRunLoopSourceRef有兩種分類(lèi)方法柠偶。
第一種按照官方文檔來(lái)分類(lèi)(就像RunLoop模型圖中那樣):
- Port-Based Sources(基于端口)
基于端口的input source監(jiān)聽(tīng)程序的Mach Ports,由系統(tǒng)內(nèi)核來(lái)自動(dòng)通知它睬关。自定義的input source則需要手動(dòng)從其他線程通知它诱担。 - Custom Input Sources(自定義)
- Cocoa Perform Selector Sources
perform selector source會(huì)在調(diào)用selector后把自己從run loop中移除出去。
第二種按照函數(shù)調(diào)用棧來(lái)分類(lèi):
- Source0 :非基于Port,處理App內(nèi)部事件,App自己負(fù)責(zé)管理(觸發(fā)),如UIEvent,CFSocket
- Source1:基于Port电爹,通過(guò)內(nèi)核和其他線程通信蔫仙,接收、分發(fā)系統(tǒng)事件,由RunLoop和內(nèi)核管理,Mach port驅(qū)動(dòng) 如CFMach丐箩、CFMessage
這兩種分類(lèi)方式其實(shí)沒(méi)有區(qū)別摇邦,只不過(guò)第一種是通過(guò)官方理論來(lái)分類(lèi),第二種是在實(shí)際應(yīng)用中通過(guò)調(diào)用函數(shù)來(lái)分類(lèi)屎勘。
上面圖中展示了Run Loop的概念結(jié)構(gòu)及各種事件源施籍。
其中input source分發(fā)異步事件給相應(yīng)的處理程序并且調(diào)用runUntilDate:方法(這個(gè)方法會(huì)在該線程關(guān)聯(lián)的NSRunLoop對(duì)象上被調(diào)用)來(lái)退出其Run Loop。
timer source分發(fā)事件到相應(yīng)的處理程序概漱,但不會(huì)引起Run Loop退出丑慎。
Run Loop除了處理各種事件外,同時(shí)會(huì)生成關(guān)于Run Loop行為相關(guān)的通知(Notifications)瓤摧,注冊(cè)run-loop observers可以接收到這些通知并根據(jù)情況去在線程上做相應(yīng)的處理竿裂。
- iOS 中所有的事件監(jiān)聽(tīng)全部由運(yùn)行循環(huán)負(fù)責(zé)。這個(gè)循環(huán)專(zhuān)門(mén)用來(lái)接收姻灶、處理App中的各種事件铛绰、事件源:
(比如 觸摸事件,定時(shí)器事件,Selector事件,通知)綁定的線程去執(zhí)行這個(gè)事件. - Run loop同時(shí)也負(fù)責(zé)autorelease pool的創(chuàng)建和釋放产喉。
- NSRunLoop的作用在于有事情做的時(shí)候使的當(dāng)前NSRunLoop的線程工作捂掰,沒(méi)有事情做讓當(dāng)前NSRunLoop的線程休眠,節(jié)省CPU資源,提高程序性能:該做事時(shí)做事,該休息時(shí)休息。
用于分發(fā)同步事件曾沈,通常這些事件發(fā)生在特定時(shí)間或者重復(fù)的時(shí)間間隔上这嚣,比如:[NSTimer scheduledTimerWithTimeInterval:target:selector:...]
5.6 CFRunLoopObserverRef類(lèi)
CFRunLoopObserverRef是觀察者,用來(lái)監(jiān)聽(tīng)RunLoop的狀態(tài)改變
CFRunLoopObserverRef可以監(jiān)聽(tīng)的狀態(tài)改變有以下幾種:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入Loop:1
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer:2
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source:4
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠:32
kCFRunLoopAfterWaiting = (1UL << 6), // 即將從休眠中喚醒:64
kCFRunLoopExit = (1UL << 7), // 即將從Loop中退出:128
kCFRunLoopAllActivities = 0x0FFFFFFFU // 監(jiān)聽(tīng)全部狀態(tài)改變
};
通過(guò)代碼來(lái)監(jiān)聽(tīng)下RunLoop中的狀態(tài)改變
-(void)observer{
//創(chuàng)建一個(gè)監(jiān)聽(tīng)對(duì)象
/*
第一個(gè)參數(shù):分配存儲(chǔ)空間的
第二個(gè)參數(shù):要監(jiān)聽(tīng)的狀態(tài) kCFRunLoopAllActivities 所有狀態(tài)
第三個(gè)參數(shù):是否要持續(xù)監(jiān)聽(tīng)
第四個(gè)參數(shù):優(yōu)先級(jí)
第五個(gè)參數(shù):回調(diào)
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"runloop進(jìn)入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"runloop要去處理timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"runloop要去處理Sources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"runloop要睡覺(jué)了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"runloop醒來(lái)啦");
break;
case kCFRunLoopExit:
NSLog(@"runloop退出");
break;
case kCFRunLoopAllActivities:
NSLog(@"kCFRunLoopAllActivities");
break;
default:
break;
}
});
//給runloop添加監(jiān)聽(tīng)者
/*
第一個(gè)參數(shù):要監(jiān)聽(tīng)哪個(gè)runloop
第二個(gè)參數(shù):監(jiān)聽(tīng)者
第三個(gè)參數(shù):要監(jiān)聽(tīng)runloop在哪種運(yùn)行模式下的狀態(tài)
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(run1) userInfo:nil repeats:YES];
CFRelease(observer);
}
自動(dòng)釋放池什么時(shí)候釋放塞俱?
通過(guò)Observer監(jiān)聽(tīng)RunLoop的狀態(tài)
在主線程即將休眠時(shí)姐帚,釋放自動(dòng)釋放池
在主線程即將喚醒時(shí),再次創(chuàng)建自動(dòng)釋放池障涯,并將之前的對(duì)象再次放入池中
6. RunLoop的運(yùn)行邏輯
在每次運(yùn)行開(kāi)啟RunLoop的時(shí)候罐旗,所在線程的RunLoop會(huì)自動(dòng)處理之前未處理的事件膳汪,并且通知相關(guān)的觀察者。
具體的順序如下:
- 通知觀察者RunLoop已經(jīng)啟動(dòng)
- 通知觀察者即將要開(kāi)始的定時(shí)器
- 通知觀察者任何即將啟動(dòng)的非基于端口的源
- 啟動(dòng)任何準(zhǔn)備好的非基于端口的源
- 如果基于端口的源準(zhǔn)備好并處于等待狀態(tài)九秀,立即啟動(dòng)遗嗽;并進(jìn)入步驟9
- 通知觀察者線程進(jìn)入休眠狀態(tài)
- 將線程置于休眠知道任一下面的事件發(fā)生:
某一事件到達(dá)基于端口的源
定時(shí)器啟動(dòng)
RunLoop設(shè)置的時(shí)間已經(jīng)超時(shí)
RunLoop被顯示喚醒 - 通知觀察者線程將被喚醒
- 處理未處理的事件
-如果用戶定義的定時(shí)器啟動(dòng),處理定時(shí)器事件并重啟RunLoop鼓蜒。進(jìn)入步驟2
如果輸入源啟動(dòng)痹换,傳遞相應(yīng)的消息
如果RunLoop被顯示喚醒而且時(shí)間還沒(méi)超時(shí),重啟RunLoop都弹。進(jìn)入步驟2 - 通知觀察者RunLoop結(jié)束娇豫。
7.RunLoop應(yīng)用
- NSTimer的使用
- ImageView推遲顯示
- 后臺(tái)常駐線程
-
檢測(cè)卡頓
...