Run Loop,感覺(jué)神一般的存在,這么久了,也沒(méi)搞懂個(gè)所以然,希望這次能夠了解些皮毛!?。?/p>
這里是官方地址!
Run loop是線程運(yùn)行的基礎(chǔ),run loop的目的是為了保證讓你空閑的線程忙碌起來(lái),或者在沒(méi)有事情可做的時(shí)候,讓線程保持睡眠。
Run loop不是完全的原子的。需要我們?cè)诤线m的時(shí)機(jī)去啟動(dòng)線程和處理事件。每一個(gè)線程都有一個(gè)run loop對(duì)象,但是只有主線程的run loop是啟動(dòng)的,其他線程的都需要手動(dòng)啟動(dòng)。
一、解剖Run Loop
run loop和它的字面意思很像。它表示一個(gè)循環(huán),讓您的線程進(jìn)入運(yùn)行事件的處理程序和響應(yīng)傳入的事件。您的代碼提供了控制語(yǔ)句去實(shí)現(xiàn)真實(shí)的run loop,換句話說(shuō),您的代碼里有類似于while或者for循環(huán)之類的去驅(qū)動(dòng)run loop。
run loop的事件源有兩種:
- 輸入源,分發(fā)異步事件,事件來(lái)源于其他線程或者其他程序
- 定時(shí)器,分發(fā)同步事件,事件由預(yù)定時(shí)間或者循環(huán)重復(fù)的事件觸發(fā)
下圖是runloop的結(jié)構(gòu)圖:

在該圖中,輸入源分發(fā)異步事件到相應(yīng)的(Port對(duì)應(yīng)handlePort、Custom對(duì)應(yīng)customSrc:等等)事件處理程序里并觸發(fā)
runUntilDate:方法去結(jié)束runloop;定時(shí)器分發(fā)事件到相應(yīng)的事件處理程序里但是不會(huì)觸發(fā)runUntilDate:去結(jié)束runloop(???)
除了處理輸入的事件,runloop還會(huì)在runloop的不同時(shí)間段發(fā)送通知,我們可以注冊(cè)相關(guān)的runloop通知來(lái)做一些額外的事情。您應(yīng)該使用Core Foundation去注冊(cè)runloop的觀察者(NSNotification是Foundation下的)。
在接下來(lái)的章節(jié)中將會(huì)對(duì)runloop做進(jìn)一步的說(shuō)明。
1、Run Loop的Modes
runloop的mode是輸入源和定時(shí)器源的集合和runloop的觀察者接收通知的集合。每次您運(yùn)行runloop,您都會(huì)指定一個(gè)mode(無(wú)論是明確地還是含蓄地)。在runloop運(yùn)行的過(guò)程中,只有事件源模式和該runloop指定模式匹配的事件源才會(huì)被監(jiān)控和分發(fā)它的事件。(相似的,只有觀察者的模式和runloop的模式匹配的才能接收到通知)模式不匹配的只能處在等待狀態(tài)。

2、輸入源
輸入源異步分發(fā)事件到你的線程。事件來(lái)源取決于輸入源的類型?;诙丝谳斎朐幢O(jiān)控你的程序的Mach端口。自定義輸入源監(jiān)控自定義的事件。對(duì)于runloop來(lái)說(shuō),它不關(guān)心你的輸入源是哪種類型,系統(tǒng)默認(rèn)都實(shí)現(xiàn)了這兩種輸入源的事件的處理程序。這兩個(gè)事件的唯一的不同之處是它們?nèi)绾伟l(fā)出信號(hào)的。基于端口輸入源由其內(nèi)核表明其類型,自定義事件必須手動(dòng)表明其類型。
當(dāng)你創(chuàng)建了一個(gè)輸入源,你要做好準(zhǔn)備,讓它在一個(gè)或者多個(gè)模式的runloop下正常運(yùn)行。runloop一直在監(jiān)控著輸入源的mode,一旦發(fā)生了模式變化,就會(huì)發(fā)生事件掛起,一直到mode匹配了才會(huì)再次觸發(fā)。
(1) 基于端口的源
蘋果Cocoa和Core Foundation框架提供了和端口相關(guān)的接口去創(chuàng)建端口對(duì)象。
比如,在Cocoa框架里,你不能直接去創(chuàng)建一個(gè)輸入源的事件,你只能使用NSPort來(lái)簡(jiǎn)單的創(chuàng)建一個(gè)端口對(duì)象并添加到runloop中。該端口對(duì)象負(fù)責(zé)創(chuàng)建和配置輸入源需要的東西。
在Core Foundation框架下,你必須手動(dòng)創(chuàng)建端口和runloop。在這兩種情況下,你需要使用相應(yīng)的端口方法(CFMachPortRef, CFMessagePortRef, 和 CFSocketRef)去創(chuàng)建合適的端口對(duì)象。
具體的使用在后面!
(2) 自定義輸入源
你需要使用類CFRunLoopSourceRef去創(chuàng)建一個(gè)自定義輸入源,可以使用一些回調(diào)函數(shù)去處理事件,因?yàn)樵诓煌臅r(shí)間段,會(huì)調(diào)用這些回調(diào)函數(shù)去注冊(cè)事件源、事件和從runloop移除該輸入源。
除了定義接收到自定義事件后的一些行為,你必須定義事件的分發(fā)機(jī)制。輸入源的該部分運(yùn)行在一個(gè)單獨(dú)的線程上,負(fù)責(zé)為輸入源提供它的數(shù)據(jù),并在數(shù)據(jù)準(zhǔn)備好處理時(shí)發(fā)出信號(hào)。事件的交付機(jī)制由你決定,但是不要太過(guò)于復(fù)雜。
具體的使用在后面!
(3) Cocoa可執(zhí)行選擇源(Selector Sources)
除了基于端口的輸入源,Cocoa定義了一個(gè)自定義的輸入源,允許在任何線程上運(yùn)行。和基于端口的輸入源一樣,執(zhí)行 selector請(qǐng)求是在目標(biāo)線程上序列化的,從而減輕了在一個(gè)線程上運(yùn)行多個(gè)方法時(shí)可能出現(xiàn)的許多同步問(wèn)題。和基于端口的輸入源不同的是,執(zhí)行 selector完畢后會(huì)自動(dòng)從runloop移除。
當(dāng)在其他線程上執(zhí)行selector時(shí),目標(biāo)線程必須有一個(gè)可活躍的runloop。對(duì)于你創(chuàng)建的線程,在代碼里需要明確的去啟動(dòng)runloop。因?yàn)樵谥骶€程里是自動(dòng)啟動(dòng)的,所以在程序執(zhí)行完applicationDidFinishLaunching:方法后,你就可以在主線程上處理事情。
runloop在每次循環(huán)時(shí)都會(huì)處理所有的在隊(duì)列中的selector,而不是只處理一次。
下面是幾個(gè)自帶的selector:
-
performSelectorOnMainThread:withObject:waitUntilDone:和performSelectorOnMainThread:withObject:waitUntilDone:modes:在程序的主線程上執(zhí)行,如果錯(cuò)過(guò)了當(dāng)前一次runloop循環(huán),會(huì)在下次循環(huán)中被立即執(zhí)行;其中waitUntilDone的參數(shù)表示添加到runloop中時(shí)是否阻塞當(dāng)前線程(主線程),yes表示阻塞,no表示不阻塞。過(guò)程如下:
- (void)viewDidLoad {
[super viewDidLoad];
self.edgesForExtendedLayout = UIRectEdgeNone;
self.view.backgroundColor = [UIColor whiteColor];
self.title = @"Run Loop";
[self executePerfomSelectorOnMainThread];
}
- (void)executePerfomSelectorOnMainThread {
[self performSelectorOnMainThreadWithWait];
[self performSelectorOnMainThreadWithNoWait];
}
- (void)performSelectorOnMainThreadWithWait {
[self performSelectorOnMainThread:@selector(logEventForWait:) withObject:@"1" waitUntilDone:YES];
NSLog(@"I am here for Wait");
}
- (void)performSelectorOnMainThreadWithNoWait {
[self performSelectorOnMainThread:@selector(logEventForNoWait:) withObject:@"1" waitUntilDone:NO];
NSLog(@"I am here for no Wait");
}
- (void)logEventForWait:(NSString *)infoStr {
NSLog(@"%@", infoStr);
sleep(2);
}
- (void)logEventForNoWait:(NSString *)infoStr {
NSLog(@"%@", infoStr);
sleep(2);
}
結(jié)果如下:
這點(diǎn)和NSNotification很像
performSelector:onThread:withObject:waitUntilDone:和performSelector:onThread:withObject:waitUntilDone:modes:在任何自定義的線程上執(zhí)行performSelector:withObject:afterDelay:和performSelector:withObject:afterDelay:inModes:在當(dāng)前線程上執(zhí)行,需要等到下一個(gè)runloop的循環(huán),它的執(zhí)行是阻塞當(dāng)前線程執(zhí)行的,但是添加是不阻塞的。另外,它的延遲運(yùn)行時(shí)間也不能做到按時(shí)出發(fā),只能盡可能以最靠近設(shè)置的延遲觸發(fā)。這里會(huì)啟動(dòng)一個(gè)定時(shí)器。
如下:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self executePerfomSelectorOnCurrentThread];
[self executePerfomSelectorOnMainThread];
}
- (void)executePerfomSelectorOnCurrentThread {
[self performSelector:@selector(logEventForPerfomSelectorOnCurrentThreadForSleep:) withObject:@"1-1" afterDelay:1];
NSLog(@"after delay 1-1");
[self performSelector:@selector(logEventForPerfomSelectorOnCurrentThread:) withObject:@"1" afterDelay:1];
[self performSelector:@selector(logEventForPerfomSelectorOnCurrentThread:) withObject:@"0" afterDelay:0];
}
- (void)logEventForPerfomSelectorOnCurrentThread:(NSString *)infoStr {
NSLog(@"%s--%@--info:%@",__func__,[NSThread currentThread],infoStr);
}
- (void)logEventForPerfomSelectorOnCurrentThreadForSleep:(NSString *)infoStr {
NSLog(@"before:%s--%@--info:%@",__func__,[NSThread currentThread],infoStr);
sleep(2);
NSLog(@"after:%s--%@--info:%@",__func__,[NSThread currentThread],infoStr);
}
結(jié)果如下:
1、可以發(fā)現(xiàn),上面在添加的時(shí)候,不會(huì)阻塞當(dāng)前線程的執(zhí)行
2、它的運(yùn)行不是準(zhǔn)時(shí)的,比如上面的兩個(gè)都是延遲1秒,但是第一個(gè)(1-1)沒(méi)有執(zhí)行結(jié)束,那么另一個(gè)就不會(huì)進(jìn)行
3、這里還調(diào)用了方法performSelectorOnMainThread:withObject:waitUntilDone:和performSelectorOnMainThread:withObject:waitUntilDone:modes:,該方法的優(yōu)先級(jí)要高很多,雖然它是后面添加的,但是它最先執(zhí)行
4、如果添加一個(gè)1秒的睡眠,又會(huì)不一樣
[self performSelector:@selector(logEventForPerfomSelectorOnCurrentThreadForSleep:) withObject:@"1-1" afterDelay:1];
NSLog(@"after delay 1-1");
[self performSelector:@selector(logEventForPerfomSelectorOnCurrentThread:) withObject:@"1" afterDelay:1];
sleep(1);
[self performSelector:@selector(logEventForPerfomSelectorOnCurrentThread:) withObject:@"0" afterDelay:0];
}
-
cancelPreviousPerformRequestsWithTarget:和cancelPreviousPerformRequestsWithTarget:selector:object:用來(lái)暫停上面的帶有延遲的方法,但是只移除當(dāng)前runloop中的,不回移除其他runloop中的。
3、時(shí)間源(定時(shí)器)
時(shí)間源會(huì)在你預(yù)設(shè)的未來(lái)的某個(gè)時(shí)間同步的把事件分發(fā)到你的線程上。定時(shí)器是通知線程自己去做一些事情的很好的例子。
盡管它是基于定時(shí)器的通知,但是它的并不是驅(qū)動(dòng)事件運(yùn)行的機(jī)制。時(shí)間源的運(yùn)行機(jī)制和事件源的機(jī)制是一樣的:需要模式mode匹配;如果當(dāng)前runloop正在執(zhí)行任務(wù),它需要等待。
你可以一次或者多次注冊(cè)時(shí)間源去處理事情。一個(gè)重復(fù)的計(jì)時(shí)器會(huì)自動(dòng)根據(jù)預(yù)定的發(fā)射時(shí)間自動(dòng)調(diào)整,也就是說(shuō)真正的觸發(fā)時(shí)間并不是你設(shè)置的時(shí)間。
4、Run Loop的觀察者
和在合適的時(shí)機(jī)觸發(fā)異步或者同步事件發(fā)生的輸入源相比,runloop的觀察者能在一些特定的時(shí)機(jī)被觸發(fā)。你可以利用runloop的這個(gè)特性去處理事件或者在線程睡眠前作一些準(zhǔn)備工作??梢詫⒁韵碌氖录ㄟ^(guò)runloop的觀察者和實(shí)際情況聯(lián)系起來(lái):
- runloop的入口
- runloop將處理計(jì)時(shí)器(時(shí)間源)timer
- runloop將處理輸入源
- runloop將進(jìn)入睡眠
- runloop將被喚醒
- runloop退出
你可以使用Core Foundation添加runloop的觀察者。你可以通過(guò)創(chuàng)建CFRunLoopObserverRef的實(shí)例來(lái)創(chuàng)建和添加runloop的觀察者。它會(huì)對(duì)你自定義的回調(diào)函數(shù)或者它感興趣的活動(dòng)進(jìn)行跟蹤。
和時(shí)間源(定時(shí)器)相似,runloop的觀察者可以使用一次或者重復(fù)使用多次,只觀察一次的觀察者會(huì)在它運(yùn)行完后被移除,但是可重復(fù)的runloop觀察者不會(huì)被移除。當(dāng)然runloop的觀察者可被執(zhí)行幾次,是由你決定的。
5、Run Loop事件執(zhí)行的順序
每次運(yùn)行線程,runloop執(zhí)行一些還在等待處理的事件和給觀察者發(fā)送通知。它所做的事情的執(zhí)行順序如下所示:
1、 告訴觀察者,要開始進(jìn)入runloop了
2、 告訴觀察者,所有的時(shí)間源(定時(shí)器)都準(zhǔn)備好了處理到來(lái)的事件
3、 告訴觀察者,所有的輸入源除了基于端口的輸入源都準(zhǔn)備好了去處理到來(lái)的事件
4、 執(zhí)行任何已經(jīng)做好準(zhǔn)備的輸入源(不包括基于端口的輸入源)
5、 如果有基于端口的輸入源已經(jīng)準(zhǔn)備好了并在等待觸發(fā),立馬執(zhí)行,并進(jìn)入第9步
6、告訴觀察者線程將要睡眠了
7、讓線程進(jìn)入睡眠,一直到出現(xiàn)下面的幾種情況里的一種,線程才被重新喚醒:
a、一個(gè)基于端口的輸入源事件到達(dá)了
b、時(shí)間源(定時(shí)器)啟動(dòng)
c、為runloop設(shè)置的超時(shí)時(shí)間過(guò)期了
d、明確的喚醒runloop
8、告訴觀察者,線程被喚醒了,但是runloop沒(méi)啟動(dòng)
9、處理等待處理的事件:
a、如果定時(shí)器啟動(dòng)了,則處理時(shí)間源事件并重啟runloop,進(jìn)入步驟2
b、如果有輸入源被觸發(fā),則分發(fā)事件
c、如果runloop被明確喚醒并且沒(méi)有超時(shí),進(jìn)入步驟2
10、告訴觀察者,runloop退出了
因?yàn)橥ㄖ窃诙〞r(shí)器和輸入源被實(shí)際觸發(fā)前發(fā)送的,所以在通知的發(fā)送和事件的真實(shí)執(zhí)行之間會(huì)有一個(gè)時(shí)間間隙。如果在這些事件間的時(shí)間很重要,可以使用sleep通知和awake通知去讓彼此關(guān)聯(lián)起來(lái)。
因?yàn)槎〞r(shí)器和其他的周期性的事件是在運(yùn)行runloop的時(shí)候分發(fā)的,因此如果繞過(guò)該runloop就會(huì)打亂那些事件的分發(fā)。
一個(gè)runloop時(shí)可以通過(guò)runloop的對(duì)象被直接喚醒的,其他事件也可能導(dǎo)致runloop被喚醒。(如上面的第7步)
二、什么時(shí)候該使用Run Loop
一般在你自己創(chuàng)建了一個(gè)線程的時(shí)候需要顯式的啟動(dòng)runloop,因?yàn)槌绦蛑械闹骶€程是默認(rèn)啟動(dòng)的,所以不需要也不希望你去顯示的啟動(dòng)。
對(duì)于自己創(chuàng)建的線程,你需要自己決定是否需要runloop,如果需要,那么創(chuàng)建它并啟動(dòng)它。然而并不是所有你創(chuàng)建的線程的runloop都需要啟動(dòng)。例如,如果您使用線程來(lái)執(zhí)行一些長(zhǎng)時(shí)間運(yùn)行和預(yù)定義的任務(wù),則不需要啟動(dòng)runloop。runloop時(shí)為了那些需要和線程有很多交互的場(chǎng)景,比如下面的幾種情況:
- 使用端口或者自定義輸入源和其他線程交互
- 在線程上使用定時(shí)器
- 使用任何類似于
performSelector…方法的場(chǎng)景 - 保持線程執(zhí)行定期任務(wù)
如果你決定去使用runloop,那么配置和設(shè)置很簡(jiǎn)單。和所有的線程一樣,你應(yīng)該有一個(gè)計(jì)劃在合適的時(shí)機(jī)去退出線程,退出一個(gè)線程要比強(qiáng)制終止一個(gè)線程更好。