下面主要是對(duì)Run Loops官方文檔的翻譯及總結(jié)珠移。
定義
run loop 是一個(gè)事件處理的循環(huán)倔丈,負(fù)責(zé)對(duì)工作進(jìn)行調(diào)度灰嫉,同時(shí)協(xié)調(diào)接收即將到來(lái)的任務(wù)郊酒。他的目的是確保線程在有任務(wù)的時(shí)候立即工作遇绞,沒(méi)有任務(wù)的時(shí)候就歇著键袱。
run loop 的管理不是完全自動(dòng)的,你可以通過(guò)代碼去運(yùn)行線程的run loop摹闽,讓他去處理特定的任務(wù)蹄咖。包括主線程在內(nèi)的所有線程,都不需要自己去創(chuàng)建run loop付鹿,他們本身內(nèi)部就有runloop對(duì)象澜汤,只不過(guò)主線程中的runloop是在程序啟動(dòng)的時(shí)候就自動(dòng)運(yùn)行起來(lái)了,而主線程外的其他線程的runloop 需要用戶自己去run舵匾。
剖析
run loop 就像它的名字一樣俊抵,它是一個(gè)在線程內(nèi)部的循環(huán),用來(lái)運(yùn)行那些負(fù)責(zé)處理即將進(jìn)入線程的事件的程序坐梯。
run loop接收來(lái)自于兩種類(lèi)型數(shù)據(jù)源的事件徽诲;一種是Input source,負(fù)責(zé)傳遞異步事件烛缔,主要來(lái)自其他線程或者其他的應(yīng)用程序馏段;另外一種是Timer source 負(fù)責(zé)傳遞同步事件,主要來(lái)計(jì)劃時(shí)間觸發(fā)或者是重復(fù)間隔觸發(fā)事件践瓷。當(dāng)兩種源類(lèi)型的事件到達(dá)后院喜,都用特定的程序來(lái)進(jìn)行處理。
上圖是run loop及其數(shù)據(jù)源的構(gòu)想結(jié)構(gòu)圖晕翠,input source 將異步事件傳遞個(gè)對(duì)應(yīng)的處理程序喷舀,最終調(diào)用runUntilDate:方法退出run loop,Timer將事件傳遞給對(duì)應(yīng)的處理程序淋肾,但不會(huì)導(dǎo)致run loop 退出硫麻。
除了處理數(shù)據(jù)源的輸入外,run loop還會(huì)為一些特性產(chǎn)生通知樊卓,只要注了run loop的觀察者都可以接受到這些通知拿愧,并且在線程內(nèi)做一些額外的工作。
-
Run Loop Modes
run loop mode 是被監(jiān)控的input source和 timer source的集合碌尔,也是需被run loop通知的觀察者的集合浇辜。每當(dāng)你運(yùn)行你的run loop 的時(shí)候,你都需要間接或直接的指定一個(gè)在內(nèi)部運(yùn)行的mode 唾戚。在run loop 運(yùn)行的過(guò)程中柳洋,只監(jiān)控與當(dāng)前mode相關(guān)聯(lián)的source,也只接受關(guān)聯(lián)source傳遞的事件叹坦。而與其他mode相關(guān)聯(lián)的source及傳遞的事件熊镣,只能等待到對(duì)應(yīng)的mode才能被處理,因此你可以通過(guò)mode來(lái)過(guò)濾出不需要的數(shù)據(jù)源傳遞的事件。
系統(tǒng)默認(rèn)提供了幾種mode绪囱,你也可以自定義mode测蹲,當(dāng)你自定義mode時(shí),一定要在mode內(nèi)添加一個(gè)或多個(gè)的 input source或者timer source 或者觀察者以確保該mode有用毕箍。
-
系統(tǒng)預(yù)定義mode
1:NSDefaultRunLoopMode 默認(rèn)mode弛房,多數(shù)情況下run loop在這種mode下運(yùn)行。
4:NSEventTrackingRunLoopMode 頁(yè)面的滑動(dòng)等運(yùn)行在這用mode下
5: NSRunLoopCommonModes 這是一個(gè)可配置的通用mode組而柑,將input source 和這個(gè)mode相關(guān)聯(lián)文捶,同時(shí)也會(huì)與組內(nèi)的其他mode相關(guān)聯(lián),在Cocoa applications中媒咳,這個(gè)組中默認(rèn)包括default, modal, event tracking modes粹排,你也可以通過(guò)CFRunLoopAddCommonMode(::)方法將自定義的mode加入到這個(gè)組中。
-
input source
input source 以異步方式傳遞事件給線程涩澡。而基于input source 的事件源主要分為兩類(lèi): Port-based input sources:監(jiān)控你應(yīng)用程序的Match端口顽耳;Custom input sources:監(jiān)控自定義的事件源。他們兩個(gè)對(duì)于系統(tǒng)來(lái)說(shuō)沒(méi)有任何差別妙同,唯一的差別是是如何發(fā)出信號(hào)射富,Port-based是由系統(tǒng)的內(nèi)核自動(dòng)發(fā)出信號(hào),Custom input 需要從其他線程手動(dòng)的發(fā)送信號(hào)粥帚。
當(dāng)你創(chuàng)建一個(gè)input source 的時(shí)候胰耗,你可將他分配給你的run loop 的一個(gè)或多個(gè)mode,mode會(huì)隨時(shí)影響到哪個(gè)input source被監(jiān)控芒涡,如果你的input source 沒(méi)有在當(dāng)前被監(jiān)控的mode中柴灯,那么事件需要等到runloop 執(zhí)行對(duì)應(yīng)的mode時(shí)才會(huì)被執(zhí)行(這就是如果你將輪播圖的定時(shí)器加入在default mode中后,如果滑動(dòng)頁(yè)面時(shí)费尽,輪播圖不會(huì)動(dòng)的原因赠群,滑動(dòng)頁(yè)面時(shí)當(dāng)前runloop運(yùn)行的是track mode,解決辦法是將定時(shí)器加入到common mode中旱幼,或者將定時(shí)器加入到新的線程查描,并且run該線程的runloop)。
Port-Based Sources柏卤、Custom Input Sources
詳見(jiàn)
Configuring a Port-Based Input Source.
Defining a Custom Input Source
除上面外叹誉,系統(tǒng)定義了一個(gè)custom source 允許用戶調(diào)用,Cocoa Perform Selector Sources闷旧,就是performSelectorOnMainThread:withObject:waitUntilDone:
等方法-
Timer source
Timer source 是預(yù)先設(shè)置好的,在未來(lái)的某個(gè)時(shí)間向線程傳遞同步事件钧唐,是一種線程提醒自己做某事的方式忙灼。
Timer source 的產(chǎn)生雖然是基于時(shí)間的通知,但是他并不是實(shí)時(shí)的,和input source一樣该园,它也需要和runloop的具體mode相關(guān)聯(lián)酸舍。如果與之關(guān)聯(lián)的mode沒(méi)有被監(jiān)控,那么timer將擁有不被觸發(fā)里初。
詳見(jiàn)Configuring Timer Sources
-
Run Loop Observers
和需要適當(dāng)?shù)耐交虍惒绞录?lái)觸發(fā)的事件源不同啃勉,Run Loop Observers是由runloop自己在運(yùn)行到對(duì)應(yīng)的的位置的時(shí)去候觸發(fā)的。用戶可以由此根據(jù)不同的時(shí)機(jī)去做一些相應(yīng)的處理双妨。主要的可以關(guān)聯(lián)的屬性如下:
- 進(jìn)入runloop
- runloop即將處理timer
- runloop即將處理input source
- runloop即將進(jìn)入睡眠
- runloop被喚醒淮阐,在處理喚醒事件前
- 從runloop中退出
詳細(xì)創(chuàng)建Configuring the Run Loop.
-
The Run Loop Sequence of Events
每次運(yùn)行runloop它處理待處理事件的整個(gè)過(guò)程如下:
- 1.通知已經(jīng)進(jìn)入runloop
- 2.通知即將處理準(zhǔn)備好的timer
- 3.通知即將處理所有非port based 的input source事件
- 4.處理那些待處理的非port based 的input source事件
- 5.如果有在等待的基于port based 的input source事件則立即去處理,然后到第9步驟
- 6.通知runloop即將進(jìn)入睡眠
- 7.保持睡眠狀態(tài)刁品,直到發(fā)生任何一個(gè)如下事件:
- 有機(jī)遇port based 的input source事件到達(dá)
- fire一個(gè)timer
- 為runloop設(shè)置的超時(shí)值到期
- runloop被明確喚醒
- 8.通知線程被喚醒
-
- 處理待處理的事件
- 如果用戶定義的timer被觸發(fā)泣特,處理timer事件,重啟runloop挑随,跳到第2步驟
- 如果input source 被觸發(fā)状您,則傳遞事件
- 如果runloop是被明確喚醒的且還沒(méi)有超時(shí),則重新運(yùn)行runloop兜挨,跳轉(zhuǎn)到第2步驟
When Would You Use a Run Loop?
主線程的runloop是在整個(gè)應(yīng)用開(kāi)始運(yùn)行的時(shí)候膏孟,由系統(tǒng)自動(dòng)啟動(dòng)的,所以需要用戶手動(dòng)啟動(dòng)的只有其他的輔助線程拌汇,而在輔助線程中也要根據(jù)情況去考慮柒桑,是否有啟動(dòng)runloop的必要,runloop的預(yù)期是你需要和線程有更多的交互担猛。
如果在下面的任意情況之一幕垦,那么就需要去啟動(dòng)runloop:
- 通過(guò)port或者custom input source 和其他線程進(jìn)行交流
- 在線程內(nèi)應(yīng)用timer
- 應(yīng)用任何的 performSelector..方法
- 保持線程以執(zhí)行定期任務(wù)
Using Run Loop Objects
-
獲得runloop對(duì)象
//獲得當(dāng)前線程的runloop對(duì)象
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
-
配置runloop
當(dāng)你運(yùn)行runloop前一定要先像其中加入至少一個(gè)input source 或者 timer,否則的話只要一運(yùn)行傅联,runloop就會(huì)停止先改。除了source 外,你也可以向其注冊(cè)觀察者蒸走,用 CFRunLoopObserverRef和`CFRunLoopAddObserver方法去創(chuàng)建與添加仇奶。
下面的代碼展示了添加觀察者的過(guò)程
void myfunc() {
NSLog(@"回調(diào)");
};
- (void)viewDidLoad {
[super viewDidLoad];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &myfunc, &context);
if (observer)
{
CFRunLoopRef cfLoop = [runloop getCFRunLoop];
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
[NSTimer scheduledTimerWithTimeInterval:1 target:self
selector:@selector(doFireTimer) userInfo:nil repeats:YES];
NSInteger loopCount = 10;
do
{
// Run the run loop 10 times to let the timer fire.
[runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
}
- (void)doFireTimer {
NSLog(@"timer fun");
}
為長(zhǎng)期存在的線程配置runloop時(shí),最好至少添加一個(gè)input source來(lái)接收消息比驻。 雖然您可以附加一個(gè)定時(shí)器進(jìn)入運(yùn)行循環(huán)该溯,但一旦定時(shí)器觸發(fā),它通常會(huì)失效别惦,這會(huì)導(dǎo)致運(yùn)行循環(huán)退出狈茉。 附加重復(fù)計(jì)時(shí)器可以使運(yùn)行循環(huán)運(yùn)行更長(zhǎng)的時(shí)間,但是會(huì)涉及定期觸發(fā)計(jì)時(shí)器以喚醒您的線程掸掸,這實(shí)際上是另一種形式的輪詢氯庆。 相比之下蹭秋,input source會(huì)等待事件到來(lái)的過(guò)程中,讓線程保持睡眠狀態(tài)堤撵。
Configuring Run Loop Sources
- Defining a Custom Input Source
- Configuring Timer Sources
- Configuring a Port-Based Input Source
略仁讨,詳情請(qǐng)查看Configuring a Port-Based Input Source
大牛的文章從源碼實(shí)現(xiàn)的角度介紹runloop,也可以去觀摩相互印證实昨,加深理解洞豁。