RunLoop的學(xué)習(xí)

什么是Run Loops

RunLoops是與線程相關(guān)聯(lián)的基礎(chǔ)部分,一個(gè)Run Loop就是事件處理循環(huán),他是用來調(diào)度和協(xié)調(diào)接收到的事件處理。使用RunLoop的目的,就是使的線程有工作需要做的時(shí)候忙碌起來,當(dāng)沒事做的時(shí)候,又可以使得線程休眠。

RunLoop管理不是自動(dòng)的。我們必須手動(dòng)設(shè)計(jì)線程代碼,在合適的時(shí)候啟動(dòng)RunLoop,并回應(yīng)到相應(yīng)的事件。Cocoa和Core Foundation都提供了run loop對(duì)象來幫助我們配置和管理線程的run loop。我們的應(yīng)用沒有必要顯式地創(chuàng)建這些對(duì)象;每個(gè)線程,包括應(yīng)用程序的主線程,都有一個(gè)與之關(guān)聯(lián)的run loop。只有子線程才需要顯式地運(yùn)行其run loop。App會(huì)將自動(dòng)配置和來運(yùn)行主線程的run loop的任務(wù)作為應(yīng)用程序啟動(dòng)處理的一部分。

一個(gè)RunLoop的結(jié)構(gòu)

Run Loop就像它的名字一樣,它使得線程進(jìn)入事件循環(huán),能對(duì)到來的事件啟動(dòng)事件處理。你的代碼中提供了流程控制說一句來實(shí)現(xiàn)run loop實(shí)實(shí)在在的循環(huán)部分,換句話說,你的代碼提供了while或者for循環(huán)來驅(qū)動(dòng)run loop。在你的循環(huán)中,你使用run loop對(duì)象在事件到達(dá)時(shí),運(yùn)行事件處理的代碼并調(diào)起已安裝的處理程序。

Run Loop接收來自兩種不同類型的源(sources)的事件

輸入源:異步傳遞事件,通常是來自不同的線程或不同的應(yīng)用的消息。輸入源異步傳遞事件到對(duì)應(yīng)的處理程序和在線程關(guān)聯(lián)的NSRunLoop對(duì)象調(diào)起runUntilDate:方法來退出事件處理。

Timer源:同步地傳遞事件,發(fā)生在每個(gè)定時(shí)器調(diào)用或周期性地調(diào)用。Timer源傳遞事件到他們的處理程序,但是不會(huì)調(diào)用run loop來退出處理。

這兩種源在事件到達(dá)時(shí)都使用應(yīng)用程序特定的處理程序來處理事件。

如下圖所示,展示了run loop和不同的源的概述結(jié)構(gòu)。

除了處理輸入源之外,run loops還發(fā)出關(guān)于run loop行為的通知。我們可以注冊(cè)成為run loop的觀察者,就可以接收這些通知和使用它在線程上做一些額外處理。我們可以使用Core Foundation在對(duì)應(yīng)的線程上注冊(cè)成為run loop的觀察者。

Run Loop Modes

Run Loop模式是一個(gè)監(jiān)視輸入源和定時(shí)器的集合和注冊(cè)成為run loop的觀察者的集合。每次要運(yùn)行run loop,都需要顯示或隱式地指定某種運(yùn)行的mode。只有與這種指定的mode關(guān)聯(lián)的源才會(huì)被監(jiān)視和允許傳遞他們的事件,同樣地,只有與這種模式關(guān)聯(lián)的觀察者都會(huì)收到run loop行為變化的通知。與其它模式想關(guān)聯(lián)的源,直到隨后在合適的模式通過循環(huán)后,都會(huì)接收到新的事件(比如,將timer加入run loop default模式下,當(dāng)滾動(dòng)時(shí),timer不會(huì)收到回調(diào),直到停止?jié)L動(dòng)回到default模式下)。

在我們的代碼中,我們通過名稱來唯一標(biāo)識(shí)mode。在Cocoa和Core Foundation中都定義了default模式和幾個(gè)常用的模式,都是通過字符串名稱來指定。我們也可以自定義模式,但是我們需要手動(dòng)添加至少一個(gè)input source/timers/observers。

我們可以通過使用mode來過濾掉我們不希望接收到來自不想要的通過run loop的源。大部分情況下,我們都是使用系統(tǒng)定義的default模式。對(duì)于子線程,我們可以使用自定義模式在關(guān)鍵性操作時(shí)阻止低優(yōu)先級(jí)的源傳遞事件。

注意:Modes是通過事件源來區(qū)分,而不是事件類型來區(qū)分。比如說,我們不能使用mode來匹配只有mouse-down事件或者只有鍵盤事件。我們可以使用modes來監(jiān)聽不同系統(tǒng)的端口,臨時(shí)掛起定時(shí)器,甚至改變正在被監(jiān)視的sources和run loop觀察者。

Input Sources

輸入源異步傳遞事件到你的線程。事件的源由輸入源的類型來決定,也就是兩種源中的其中一種:

Port-based:基于端口號(hào)的輸入源監(jiān)聽?wèi)?yīng)用程序的Mach端口。

Custom Input Sources:自定義輸入源監(jiān)聽自定義的事件源。

系統(tǒng)通常實(shí)現(xiàn)了這兩種輸入源。唯一的不同點(diǎn)是它們是如何被發(fā)出信號(hào)的。port-based源是由內(nèi)核(kernel)自動(dòng)發(fā)出信號(hào),而custom sources必須手動(dòng)從其它線程發(fā)出信號(hào)。

當(dāng)我們創(chuàng)建輸入源時(shí),可以指定mode。Modes會(huì)影響任何時(shí)刻被監(jiān)視的輸入源。大部分情況下,我們都讓run loop在default mode下運(yùn)行,但是也可以指定自定義的mode。如果一個(gè)輸入源不是當(dāng)前所監(jiān)視的model,它所產(chǎn)生的任何事件都會(huì)被保留直接進(jìn)入正常的mode。

Port-Based Sources

Cocoa和Core Foundation提供了內(nèi)建支持,可以使用與port相關(guān)的對(duì)象和函數(shù)來創(chuàng)建基于端口的輸入源。舉個(gè)例子,在Cocoa中永遠(yuǎn)不需要手動(dòng)創(chuàng)建輸入源。我們只需要簡(jiǎn)單地創(chuàng)建一個(gè)port對(duì)象和使用NSPort的方法。port對(duì)象為我們處理所需要的輸入源的創(chuàng)建和配置。

在Core Foundation中,我們必須手動(dòng)創(chuàng)建port和source。在這兩種情況下,我們可以使用與port opaque type關(guān)聯(lián)的函數(shù)(CFMessagePortRef, or CFSocketRef) 來創(chuàng)建合適的對(duì)象。

Custom Input Sources

在Core Foundation中,要?jiǎng)?chuàng)建自定義輸入源,我們必須使用與CFRunLoopSourceRef關(guān)聯(lián)的函數(shù)。我們配置自定義輸入源可以使用幾個(gè)回調(diào)函數(shù)。Core Foundation會(huì)在不同點(diǎn)回調(diào)這些函數(shù)來配置source,處理任何到達(dá)的事件和銷毀已從run loop移除的source。

除了定義在事件到達(dá)時(shí)自定義源的行為之外,我們也必須定義事件傳遞機(jī)制。這部分源運(yùn)行在單獨(dú)的線程,負(fù)責(zé)提供輸入源的數(shù)據(jù),當(dāng)數(shù)據(jù)準(zhǔn)備好可以處理時(shí),signaling(通知相關(guān)線程)這個(gè)消息。事件傳遞機(jī)制是我們自己來決定,但是不需要過于復(fù)雜。

Cocoa Perform Selector Source

除了基于端口的源之外,Cocoa還定義了自定義輸入源允許我們?cè)谌我饩€程上執(zhí)行selector。就像port-based源一樣,執(zhí)行selector請(qǐng)求會(huì)在目標(biāo)線程上序列化,以減少在同一個(gè)線程中出現(xiàn)多個(gè)方法同步執(zhí)行的問題。與port-based源不同的是,執(zhí)行selector源在執(zhí)行完畢后會(huì)自動(dòng)將自己從run loop中移除。

當(dāng)執(zhí)行在其它線程執(zhí)行selector時(shí),目標(biāo)線程必須要有運(yùn)行的run loop。當(dāng)我們創(chuàng)建線程時(shí),這意味著直到啟動(dòng)了run loop都會(huì)顯式地執(zhí)行selector代碼。

Run Loop每次經(jīng)過一個(gè)循環(huán),就會(huì)處理隊(duì)列中所有的selector,而不僅僅是處理一個(gè)。

Timer Sources

Timer源在未來設(shè)定的時(shí)間會(huì)同步地傳遞事件到你的線程。Timers是線程通知自己去做一些事情的一種方式。比如說,搜索框可以使用定時(shí)器來初始化在一定時(shí)間就自動(dòng)搜索,以便提供更多地聯(lián)想詞給用戶。

盡管它發(fā)送基于時(shí)間的通知,但定時(shí)器并不是一種實(shí)時(shí)的機(jī)制。像輸入源一樣,定時(shí)器只有與run loop的mode一樣才會(huì)發(fā)送通知。如果timer在run loop中并不是所被監(jiān)視的mode,它不會(huì)觸發(fā)定時(shí)器,直到run loop的mode與timer所支持的mode一樣。

同樣地,如果run loop正在處理中,timer已經(jīng)fire了,這時(shí)候會(huì)被中斷,直到下一次通過run loop才會(huì)調(diào)志處理程序。如果run loop已經(jīng)不再運(yùn)行了,則timer永遠(yuǎn)不會(huì)再fire。

我們可以配置timer只產(chǎn)生事件一次或者重復(fù)產(chǎn)生。重復(fù)的timer會(huì)自動(dòng)根據(jù)調(diào)度的firing time自動(dòng)調(diào)度,而不是真實(shí)的firing time。比如說,如果一個(gè)timer在特定的時(shí)間調(diào)度,然后每5秒重復(fù)一次。如果firing time被延遲導(dǎo)致缺少一或多次調(diào)用,那么timer在缺失的周期中只會(huì)調(diào)用一次。

Run Loop Observers

與sources在適當(dāng)時(shí)機(jī)異步或同步發(fā)出事件不同,observers在run loop本身執(zhí)行期間,會(huì)在特定的地方發(fā)出。你可能需要到run loop observers去準(zhǔn)備線程處理特定的事件或者在進(jìn)入睡眠之前。我們可以通過以下事件來關(guān)聯(lián)run loop observers:

進(jìn)入run loop

run loop將要處理timer

run loop將要處理輸入源

run loop將要進(jìn)入睡眠

run loop被喚醒,但是還沒有處理事件

退出run loop

我們可以通過Core Foundation來添加run loop observers。要?jiǎng)?chuàng)建run loop observer,可以通過CFRunLoopObserverRef來創(chuàng)建新的實(shí)例。這個(gè)類型會(huì)跟蹤你所定義的回調(diào)函數(shù)和所感興趣的活動(dòng)。

與timers類型,run-loop observers可以使用一次或者重復(fù)多次。一次性的observer會(huì)在fire之后自動(dòng)從run loop移除,而重復(fù)性的observer會(huì)繼續(xù)持有。

The Run Loop Sequence Of Events

本小節(jié)講的是RunLoop事件順序。每次運(yùn)行它,你的線程的run loop處理待處理的事件和給所有attached observers發(fā)出通知。處理的順序如下:

通知observers run loop已經(jīng)進(jìn)入

通知observers timers準(zhǔn)備要fire

通知observers有不是基于port-based的輸入源即將要fire

fire任何已經(jīng)準(zhǔn)備好的non-port-based輸入源

如果port-based輸入源準(zhǔn)備好且等待fire,則立即處理這個(gè)事件。然后進(jìn)入步驟9

通知observers線程即將進(jìn)入睡眠

讓線程進(jìn)入睡眠,直到以下任何一種事件到達(dá):

port-based輸入源有事件到達(dá)

timer fire

run loop超時(shí)

run loop被顯式喚醒

通知observers線程被喚醒

處理待處理的事件:

如果用戶定義的timer fired了,處理timer事件并重新啟動(dòng)循環(huán)。進(jìn)入步驟2

如果輸入源fired了,則傳遞事件

如果run loop被顯式喚醒,但是又未超時(shí),則重啟循環(huán),進(jìn)入步驟2

通知observers run loop退出

由于observer對(duì)timer和輸入源的通知會(huì)在事件真正發(fā)生之前被傳遞,這樣就產(chǎn)生了間隙。如果這個(gè)間隙是很關(guān)鍵的,那么我們可以通過使用sleep和awake-from-sleep通知來幫助我們糾正這個(gè)時(shí)間間隔問題。

什么時(shí)候應(yīng)該使用run loop呢?

只有當(dāng)我們需要?jiǎng)?chuàng)建子線程的時(shí)候,才會(huì)需要到顯示地運(yùn)行run loop。應(yīng)用程序的主線程的run loop是應(yīng)用啟動(dòng)的基礎(chǔ)任務(wù),在啟動(dòng)時(shí)就會(huì)自動(dòng)啟動(dòng)run loop。所以我們不需要手動(dòng)啟動(dòng)主線程的run loop。

對(duì)于子線程,我們需要確定線程是否需要run loop,如果需要,則配置它并啟動(dòng)它。我們并不問題需要啟動(dòng)run loop的。比如說,如果我們開一個(gè)子線程去執(zhí)行一些長(zhǎng)時(shí)間的和預(yù)先決定的任務(wù),我們可能不需要啟動(dòng)run loop。Run loop是用于那么需要在線程中有更多地交互的場(chǎng)景。比如說,我們會(huì)在下面的任何一種場(chǎng)景中需要開啟run loop:

使用端口源或者自定義輸入源與其它線程通信

在線程中使用定時(shí)器

使用Cocoa中的任何performSelector…方法

保持線程來執(zhí)行周期性的任務(wù)

Using Run Loop Objects

Run Loop對(duì)象給添加輸入源、定時(shí)器和觀察者到run loop提供了主接口。每個(gè)線程都有一個(gè)單獨(dú)的run loop與之關(guān)聯(lián)(對(duì)于子線程,若沒有調(diào)用過任何獲取run loop的方法是不會(huì)有run loop的,只有調(diào)用過,才會(huì)創(chuàng)建或者直接使用)。

在Cocoa中,通過NSRunLoop來創(chuàng)建實(shí)例,在low-level應(yīng)用中,可以使用CFRunLoopRef類型,它是指針。

Getting A Run Loop Object

通過以下兩種方式來獲取run loop對(duì)象:

在Cocoa中,使用[NSRunLoop currentRunLoop]獲取

使用CFRunLoopGetCurrent()函數(shù)獲取

配置RunLoop

在子線程運(yùn)行run loop之前,你必須至少添加一種輸入源或者定時(shí)器。如果run loop沒有任何的源需要監(jiān)視,它就會(huì)立刻退出。

除了添加sources之外,你還可以添加觀察者來檢測(cè)runloop不同的執(zhí)行狀態(tài)。要添加觀察者,可以使用CFRunLoopObserverRef指針類型和使用CFRunLoopAddObserver函數(shù)來添加到run loop中。我們只能通過Core Foundation來創(chuàng)建run loop觀察者,即使是Cocoa應(yīng)用。

下面這段代碼展示主線程如何添加觀察者到run loop以及如何創(chuàng)建run loop觀察者:

Starting the Run Loop

只有子線程才有可能需要啟動(dòng)run loop。Run loop必須至少有一種輸入源或者timer源來監(jiān)視。如果沒有任何源,則run loop會(huì)退出。

下面的幾種方式可以啟動(dòng)run loop:

無(wú)條件地:無(wú)條件進(jìn)入run loop是最簡(jiǎn)單的方式,但也是最不希望這么做的,因?yàn)檫@樣會(huì)導(dǎo)致run loop會(huì)進(jìn)入永久地循環(huán)??梢蕴砑?、刪除輸入源和timer源,但是只能通過kill掉run loop才能停止。而且還不能使用自定義mode。

限時(shí):與無(wú)條件運(yùn)行run loop不同,最好是給run loop添加一個(gè)超時(shí)時(shí)間。

在特定的mode:除了添加超時(shí)時(shí)間,還可以指定mode。

Exiting the Run Loop

有兩種方法使run loop在處理事件之前,退出run loop:

給run loop設(shè)定超時(shí)時(shí)間

告訴run loop要stop

設(shè)定超時(shí)時(shí)間是比較推薦的。我們可以通過CFRunLoopStop函數(shù)來停止run loop。

Thread Safety and Run Loop Objects

Core Foundation中的Run Loop API是線程安全的(以CF開頭的API),而Cocoa中的NSRunLoop不是線程安全的。

Configuring Run Loop Sources

下面是展示如何配置不同類型的輸入源。

Defining a Custom Input Source

創(chuàng)建自定義輸入源涉及到以下部分:

想要處理的輸入源的信息

讓感興趣的客戶端知道如何聯(lián)系輸入源的調(diào)度程序

執(zhí)行任何客戶端發(fā)送的請(qǐng)求處理程序


使輸入源失效的取消程序


二、舉例說明Runloop的優(yōu)點(diǎn)。

一般情況下,當(dāng)我們使用NSRunLoop的時(shí)候,代碼如下所示:

do {

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDate distantFuture]];

} while (!done);

在上面的代碼中,參數(shù)done為NO的時(shí)候,當(dāng)前runloop會(huì)一直接收處理其他輸入源,處理輸入源之后會(huì)再回到runloop中等待其他的輸入源;除非done為NO,否則當(dāng)前流程一直再runloop中。

如下面的代碼片段所示,有三個(gè)按鈕,分別對(duì)應(yīng)如下三個(gè)action消息,buttonNormalThreadTestPressed,buttonRunloopPressed,buttonTestPressed。

buttonNormalThreadTestPressed:?jiǎn)?dòng)一個(gè)線程,在while循環(huán)中等待線程執(zhí)行完再接著往下運(yùn)行。

buttonRunloopPressed:?jiǎn)?dòng)一個(gè)線程,使用runloop,等待線程執(zhí)行完再接著往下運(yùn)行。

buttonTestPressed:僅僅打印兩條日志,用來測(cè)試UI是否能立即響應(yīng)的。

在本測(cè)試中,待程序運(yùn)行后,做如下操作對(duì)比:

1、點(diǎn)擊buttonNormalThreadTestPressed,然后立刻點(diǎn)擊buttonTestPressed,查看日志輸出。

2、待1完成后,點(diǎn)擊buttonRunloopPressed,然后立刻點(diǎn)擊buttonTestPressed,查看日志輸出,跟1的日志做對(duì)比,即可以發(fā)現(xiàn)步驟2即使線程沒有完成,在runloop等待過程中,界面仍然能夠響應(yīng)。

BOOLthreadProcess1Finished =NO;

-(void)threadProce1{

NSLog(@"Enter threadProce1.");

for(inti=0; i<5;i++) {

NSLog(@"InthreadProce1 count = %d.", i);

sleep(1);

}

threadProcess1Finished=YES;

NSLog(@"Exit threadProce1.");

}

BOOLthreadProcess2Finished =NO;

-(void)threadProce2{

NSLog(@"Enter threadProce2.");

for(inti=0; i<5;i++) {

NSLog(@"InthreadProce2 count = %d.", i);

sleep(1);

}

threadProcess2Finished=YES;

NSLog(@"Exit threadProce2.");

}

- (IBAction)buttonNormalThreadTestPressed:(UIButton*)sender {

NSLog(@"EnterbuttonNormalThreadTestPressed");

threadProcess1Finished=NO;

NSLog(@"Start a new thread.");

[NSThreaddetachNewThreadSelector:@selector(threadProce1)

toTarget:self

withObject:nil];

// 通常等待線程處理完后再繼續(xù)操作的代碼如下面的形式。

// 在等待線程threadProce1結(jié)束之前,調(diào)用buttonTestPressed,界面沒有響應(yīng),直到threadProce1運(yùn)行完,才打印buttonTestPressed里面的日志。

while(!threadProcess1Finished) {

[NSThreadsleepForTimeInterval: 0.5];

}

NSLog(@"ExitbuttonNormalThreadTestPressed");

}

- (IBAction)buttonRunloopPressed:(id)sender {

NSLog(@"Enter buttonRunloopPressed");

threadProcess2Finished=NO;

NSLog(@"Start a new thread.");

[NSThreaddetachNewThreadSelector:@selector(threadProce2)

toTarget:self

withObject:nil];

// 使用runloop,情況就不一樣了。

// 在等待線程threadProce2結(jié)束之前,調(diào)用buttonTestPressed,界面立馬響應(yīng),并打印buttonTestPressed里面的日志。

// 這就是runloop的神奇所在

while(!threadProcess2Finished) {

NSLog(@"Begin runloop");

[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopMode

beforeDate: [NSDatedistantFuture]];

NSLog(@"End runloop.");

}

NSLog(@"Exit buttonRunloopPressed");

}

- (IBAction)buttonTestPressed:(id)sender{

NSLog(@"Enter buttonTestPressed");

NSLog(@"Exit buttonTestPressed");

}

日志信息如下:

2013-04-07 14:25:22.829 Runloop[657:11303] EnterbuttonNormalThreadTestPressed

2013-04-07 14:25:22.830 Runloop[657:11303] Start a new thread.

2013-04-07 14:25:22.831 Runloop[657:1250f] Enter threadProce1.

2013-04-07 14:25:22.832 Runloop[657:1250f] In threadProce1 count = 0.

2013-04-07 14:25:23.833 Runloop[657:1250f] In threadProce1 count = 1.

2013-04-07 14:25:24.834 Runloop[657:1250f] In threadProce1 count = 2.

2013-04-07 14:25:25.835 Runloop[657:1250f] In threadProce1 count = 3.

2013-04-07 14:25:26.837 Runloop[657:1250f] In threadProce1 count = 4.

2013-04-07 14:25:27.839 Runloop[657:1250f] Exit threadProce1.

2013-04-07 14:25:27.840 Runloop[657:11303]ExitbuttonNormalThreadTestPressed

2013-04-07 14:25:27.841 Runloop[657:11303]EnterbuttonTestPressed

2013-04-07 14:25:27.842 Runloop[657:11303] Exit buttonTestPressed

2013-04-07 14:25:27.843 Runloop[657:11303] Enter buttonTestPressed

2013-04-07 14:25:27.844 Runloop[657:11303] Exit buttonTestPressed

2013-04-07 14:43:41.790 Runloop[657:11303] Enter buttonRunloopPressed

2013-04-07 14:43:41.790 Runloop[657:11303] Start a new thread.

2013-04-07 14:43:41.791 Runloop[657:11303] Begin runloop

2013-04-07 14:43:41.791 Runloop[657:14f0b] Enter threadProce2.

2013-04-07 14:43:41.792 Runloop[657:14f0b] In threadProce2 count = 0.

2013-04-07 14:43:42.542 Runloop[657:11303] End runloop.

2013-04-07 14:43:42.543 Runloop[657:11303] Begin runloop

2013-04-07 14:43:42.694 Runloop[657:11303]Enter buttonTestPressed

2013-04-07 14:43:42.694 Runloop[657:11303]Exit buttonTestPressed

2013-04-07 14:43:42.695 Runloop[657:11303] End runloop.

2013-04-07 14:43:42.696 Runloop[657:11303] Begin runloop

2013-04-07 14:43:42.793 Runloop[657:14f0b] In threadProce2 count = 1.

2013-04-07 14:43:43.326 Runloop[657:11303] End runloop.

2013-04-07 14:43:43.327 Runloop[657:11303] Begin runloop

2013-04-07 14:43:43.438 Runloop[657:11303]Enter buttonTestPressed

2013-04-07 14:43:43.438 Runloop[657:11303]Exit buttonTestPressed

2013-04-07 14:43:43.439 Runloop[657:11303] End runloop.

2013-04-07 14:43:43.440 Runloop[657:11303] Begin runloop

2013-04-07 14:43:43.795 Runloop[657:14f0b] In threadProce2 count = 2.

2013-04-07 14:43:44.797 Runloop[657:14f0b] In threadProce2 count = 3.

2013-04-07 14:43:45.798 Runloop[657:14f0b] In threadProce2 count = 4.

2013-04-07 14:43:46.800 Runloop[657:14f0b] Exit threadProce2.

三、Runloop簡(jiǎn)單實(shí)例:

- (void)viewDidLoad

{

[superviewDidLoad];

// Doany additional setup after loading the view, typically from a nib.

[NSThreaddetachNewThreadSelector:@selector(newThreadProcess)

toTarget:self

withObject:nil];

}

- (void)newThreadProcess

{

@autoreleasepool{

////獲得當(dāng)前thread的Runloop

NSRunLoop* myRunLoop = [NSRunLoopcurrentRunLoop];

//設(shè)置Run loop observer的運(yùn)行環(huán)境

CFRunLoopObserverContextcontext = {0,self,NULL,NULL,NULL};

//創(chuàng)建Run loop observer對(duì)象

//第一個(gè)參數(shù)用于分配observer對(duì)象的內(nèi)存

//第二個(gè)參數(shù)用以設(shè)置observer所要關(guān)注的事件,詳見回調(diào)函數(shù)myRunLoopObserver中注釋

//第三個(gè)參數(shù)用于標(biāo)識(shí)該observer是在第一次進(jìn)入runloop時(shí)執(zhí)行還是每次進(jìn)入run loop處理時(shí)均執(zhí)行

//第四個(gè)參數(shù)用于設(shè)置該observer的優(yōu)先級(jí)

//第五個(gè)參數(shù)用于設(shè)置該observer的回調(diào)函數(shù)

//第六個(gè)參數(shù)用于設(shè)置該observer的運(yùn)行環(huán)境

CFRunLoopObserverRefobserver =CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0, &myRunLoopObserver, &context);

if(observer)

{

//將Cocoa的NSRunLoop類型轉(zhuǎn)換成CoreFoundation的CFRunLoopRef類型

CFRunLoopRefcfRunLoop = [myRunLoopgetCFRunLoop];

//將新建的observer加入到當(dāng)前thread的runloop

CFRunLoopAddObserver(cfRunLoop, observer,kCFRunLoopDefaultMode);

}

//

[NSTimerscheduledTimerWithTimeInterval:1

target:self

selector:@selector(timerProcess)

userInfo:nil

repeats:YES];

NSIntegerloopCount =2;

do{

//啟動(dòng)當(dāng)前thread的loop直到所指定的時(shí)間到達(dá),在loop運(yùn)行時(shí),runloop會(huì)處理所有來自與該run loop聯(lián)系的inputsource的數(shù)據(jù)

//對(duì)于本例與當(dāng)前run loop聯(lián)系的inputsource只有一個(gè)Timer類型的source。

//該Timer每隔1秒發(fā)送觸發(fā)事件給runloop,run loop檢測(cè)到該事件時(shí)會(huì)調(diào)用相應(yīng)的處理方法。

//由于在run loop添加了observer且設(shè)置observer對(duì)所有的runloop行為都感興趣。

//當(dāng)調(diào)用runUnitDate方法時(shí),observer檢測(cè)到runloop啟動(dòng)并進(jìn)入循環(huán),observer會(huì)調(diào)用其回調(diào)函數(shù),第二個(gè)參數(shù)所傳遞的行為是kCFRunLoopEntry。

//observer檢測(cè)到runloop的其它行為并調(diào)用回調(diào)函數(shù)的操作與上面的描述相類似。

[myRunLooprunUntilDate:[NSDatedateWithTimeIntervalSinceNow:5.0]];

//當(dāng)run loop的運(yùn)行時(shí)間到達(dá)時(shí),會(huì)退出當(dāng)前的runloop。observer同樣會(huì)檢測(cè)到runloop的退出行為并調(diào)用其回調(diào)函數(shù),第二個(gè)參數(shù)所傳遞的行為是kCFRunLoopExit。

loopCount--;

}while(loopCount);

}

}

voidmyRunLoopObserver(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity,void*info)

{

switch(activity) {

//The entrance of the run loop, beforeentering the event processing loop.

//This activity occurs once for each callto CFRunLoopRun and CFRunLoopRunInMode

casekCFRunLoopEntry:

NSLog(@"run loop entry");

break;

//Inside the event processing loop beforeany timers are processed

casekCFRunLoopBeforeTimers:

NSLog(@"run loop before timers");

break;

//Inside the event processing loop beforeany sources are processed

casekCFRunLoopBeforeSources:

NSLog(@"run loop before sources");

break;

//Inside the event processing loop beforethe run loop sleeps, waiting for a source or timer to fire.

//This activity does not occur ifCFRunLoopRunInMode is called with a timeout of 0 seconds.

//It also does not occur in a particulariteration of the event processing loop if a version 0 source fires

casekCFRunLoopBeforeWaiting:

NSLog(@"run loop before waiting");

break;

//Inside the event processing loop afterthe run loop wakes up, but before processing the event that woke it up.

//This activity occurs only if the run loopdid in fact go to sleep during the current loop

casekCFRunLoopAfterWaiting:

NSLog(@"run loop after waiting");

break;

//The exit of the run loop, after exitingthe event processing loop.

//This activity occurs once for each callto CFRunLoopRun and CFRunLoopRunInMode

casekCFRunLoopExit:

NSLog(@"run loop exit");

break;

/*

A combination of all the precedingstages

case kCFRunLoopAllActivities:

break;

*/

default:

break;

}

}

- (void)timerProcess{

for(inti=0; i<5; i++) {

NSLog(@"In timerProcess count = %d.", i);

sleep(1);

}

}

調(diào)試打印信息如下:

2012-12-18 09:51:14.174 Texta[645:14807] run loop entry

2012-12-18 09:51:14.175 Texta[645:14807] run loop before timers

2012-12-18 09:51:14.176 Texta[645:14807] run loop before sources

2012-12-18 09:51:14.177 Texta[645:14807] run loop before waiting

2012-12-18 09:51:15.174 Texta[645:14807] run loop after waiting

2012-12-18 09:51:15.176 Texta[645:14807] In timerProcess count = 0.

2012-12-18 09:51:16.178 Texta[645:14807] In timerProcess count = 1.

2012-12-18 09:51:17.181 Texta[645:14807] In timerProcess count = 2.

2012-12-18 09:51:18.183 Texta[645:14807] In timerProcess count = 3.

2012-12-18 09:51:19.185 Texta[645:14807] In timerProcess count = 4.

2012-12-18 09:51:20.187 Texta[645:14807] run loop exit

2012-12-18 09:51:20.189 Texta[645:14807] run loop entry

2012-12-18 09:51:20.190 Texta[645:14807] run loop before timers

2012-12-18 09:51:20.191 Texta[645:14807] run loop before sources

2012-12-18 09:51:20.191 Texta[645:14807] run loop before waiting

2012-12-18 09:51:21.174 Texta[645:14807] run loop after waiting

2012-12-18 09:51:21.176 Texta[645:14807] In timerProcess count = 0.

2012-12-18 09:51:22.178 Texta[645:14807] In timerProcess count = 1.

2012-12-18 09:51:23.181 Texta[645:14807] In timerProcess count = 2.

2012-12-18 09:51:24.183 Texta[645:14807] In timerProcess count = 3.

2012-12-18 09:51:25.185 Texta[645:14807] In timerProcess count = 4.

2012-12-18 09:51:26.187 Texta[645:14807] run loop exit

四、Runloop可以阻塞線程,等待其他線程執(zhí)行后再執(zhí)行。

比如:

BOOLStopFlag =NO;

- (void)viewDidLoad

{

[superviewDidLoad];

// Doany additional setup after loading the view, typically from a nib.

StopFlag=NO;

NSLog(@"Start a new thread.");

[NSThreaddetachNewThreadSelector:@selector(newThreadProc)

toTarget:self

withObject:nil];

while(!StopFlag) {

NSLog(@"Beginrunloop");

[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopMode

beforeDate: [NSDatedistantFuture]];

NSLog(@"Endrunloop.");

}

NSLog(@"OK");

}

-(void)newThreadProc{

NSLog(@"Enter newThreadProc.");

for(inti=0; i<10; i++) {

NSLog(@"InnewThreadProc count = %d.", i);

sleep(1);

}

StopFlag=YES;

NSLog(@"Exit newThreadProc.");

}

}

調(diào)試打印信息如下:

2012-12-18 08:50:34.220 Runloop[374:11303] Start a new thread.

2012-12-18 08:50:34.222 Runloop[374:11303] Begin runloop

2012-12-18 08:50:34.222 Runloop[374:14b03] Enter newThreadProc.

2012-12-18 08:50:34.223 Runloop[374:14b03] In newThreadProc count = 0.

2012-12-18 08:50:35.225 Runloop[374:14b03] In newThreadProc count = 1.

2012-12-18 08:50:36.228 Runloop[374:14b03] In newThreadProc count = 2.

2012-12-18 08:50:37.230 Runloop[374:14b03] In newThreadProc count = 3.

2012-12-18 08:50:38.233 Runloop[374:14b03] In newThreadProc count = 4.

2012-12-18 08:50:39.235 Runloop[374:14b03] In newThreadProc count = 5.

2012-12-18 08:50:40.237 Runloop[374:14b03] In newThreadProc count = 6.

2012-12-18 08:50:41.240 Runloop[374:14b03] In newThreadProc count = 7.

2012-12-18 08:50:42.242 Runloop[374:14b03] In newThreadProc count = 8.

2012-12-18 08:50:43.245 Runloop[374:14b03] In newThreadProc count = 9.

2012-12-18 08:50:44.247 Runloop[374:14b03] Exit newThreadProc.

2012-12-18 08:51:00.000 Runloop[374:11303] End runloop.

2012-12-18 08:51:00.001 Runloop[374:11303] OK

從調(diào)試打印信息可以看到,while循環(huán)后執(zhí)行的語(yǔ)句會(huì)在很長(zhǎng)時(shí)間后才被執(zhí)行。因?yàn)?,改變變量StopFlag的值,runloop對(duì)象根本不知道,runloop在這個(gè)時(shí)候未被喚醒。有其他事件在某個(gè)時(shí)點(diǎn)喚醒了主線程,這才結(jié)束了while循環(huán),但延緩的時(shí)長(zhǎng)總是不定的。。

將代碼稍微修改一下:

[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopMode

beforeDate:[NSDatedateWithTimeIntervalSinceNow:1]];

縮短runloop的休眠時(shí)間,看起來解決了上面出現(xiàn)的問題。

但這樣會(huì)導(dǎo)致runloop被經(jīng)常性的喚醒,違背了runloop的設(shè)計(jì)初衷。runloop的目的就死讓你的線程在有工作的時(shí)候忙于工作,而沒工作的時(shí)候處于休眠狀態(tài)。

最后,看下下面正確的寫法:

BOOLStopFlag =NO;

- (void)viewDidLoad

{

[superviewDidLoad];

// Doany additional setup after loading the view, typically from a nib.

StopFlag=NO;

NSLog(@"Start a new thread.");

[NSThreaddetachNewThreadSelector:@selector(newThreadProc)

toTarget:self

withObject:nil];

while(!StopFlag) {

NSLog(@"Beginrunloop");

[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopMode

beforeDate: [NSDatedistantFuture]];

NSLog(@"Endrunloop.");

}

NSLog(@"OK");

}

-(void)newThreadProc{

NSLog(@"Enter newThreadProc.");

for(inti=0; i<10; i++) {

NSLog(@"InnewThreadProc count = %d.", i);

sleep(1);

}

[selfperformSelectorOnMainThread:@selector(setEnd)

withObject:nil

waitUntilDone:NO];

NSLog(@"Exit newThreadProc.");

}

-(void)setEnd{

StopFlag=YES;

}

調(diào)試打印信息如下:

2012-12-18 09:05:17.161 Runloop[410:11303] Start a new thread.

2012-12-18 09:05:17.163 Runloop[410:14a03] Enter newThreadProc.

2012-12-18 09:05:17.164 Runloop[410:14a03] In newThreadProc count = 0.

2012-12-18 09:05:17.165 Runloop[410:11303] Begin runloop

2012-12-18 09:05:18.166 Runloop[410:14a03] In newThreadProc count = 1.

2012-12-18 09:05:19.168 Runloop[410:14a03] In newThreadProc count = 2.

2012-12-18 09:05:20.171 Runloop[410:14a03] In newThreadProc count = 3.

2012-12-18 09:05:21.173 Runloop[410:14a03] In newThreadProc count = 4.

2012-12-18 09:05:22.175 Runloop[410:14a03] In newThreadProc count = 5.

2012-12-18 09:05:23.178 Runloop[410:14a03] In newThreadProc count = 6.

2012-12-18 09:05:24.180 Runloop[410:14a03] In newThreadProc count = 7.

2012-12-18 09:05:25.182 Runloop[410:14a03] In newThreadProc count = 8.

2012-12-18 09:05:26.185 Runloop[410:14a03] In newThreadProc count = 9.

2012-12-18 09:05:27.188 Runloop[410:14a03] Exit newThreadProc.

2012-12-18 09:05:27.188 Runloop[410:11303] End runloop.

2012-12-18 09:05:27.189 Runloop[410:11303] OK

把直接設(shè)置變量,改為向主線程發(fā)送消息,喚醒runloop,延時(shí)問題解決。

參考博客1

參考博客2參考博客3

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容