RunLoop的學習

什么是Run Loops

RunLoops是與線程相關聯的基礎部分淋样,一個Run Loop就是事件處理循環(huán)嘹悼,他是用來調度和協調接收到的事件處理排苍。使用RunLoop的目的枝笨,就是使的線程有工作需要做的時候忙碌起來袁铐,當沒事做的時候,又可以使得線程休眠横浑。

RunLoop管理不是自動的剔桨。我們必須手動設計線程代碼,在合適的時候啟動RunLoop徙融,并回應到相應的事件洒缀。Cocoa和Core Foundation都提供了run loop對象來幫助我們配置和管理線程的run loop。我們的應用沒有必要顯式地創(chuàng)建這些對象欺冀;每個線程树绩,包括應用程序的主線程,都有一個與之關聯的run loop隐轩。只有子線程才需要顯式地運行其run loop饺饭。App會將自動配置和來運行主線程的run loop的任務作為應用程序啟動處理的一部分。

一個RunLoop的結構

Run Loop就像它的名字一樣职车,它使得線程進入事件循環(huán)瘫俊,能對到來的事件啟動事件處理鹊杖。你的代碼中提供了流程控制說一句來實現run loop實實在在的循環(huán)部分,換句話說扛芽,你的代碼提供了while或者for循環(huán)來驅動run loop骂蓖。在你的循環(huán)中,你使用run loop對象在事件到達時胸哥,運行事件處理的代碼并調起已安裝的處理程序涯竟。

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

輸入源:異步傳遞事件赡鲜,通常是來自不同的線程或不同的應用的消息空厌。輸入源異步傳遞事件到對應的處理程序和在線程關聯的NSRunLoop對象調起runUntilDate:方法來退出事件處理。

Timer源:同步地傳遞事件银酬,發(fā)生在每個定時器調用或周期性地調用嘲更。Timer源傳遞事件到他們的處理程序,但是不會調用run loop來退出處理揩瞪。

這兩種源在事件到達時都使用應用程序特定的處理程序來處理事件赋朦。

如下圖所示,展示了run loop和不同的源的概述結構李破。

除了處理輸入源之外宠哄,run loops還發(fā)出關于run loop行為的通知。我們可以注冊成為run loop的觀察者嗤攻,就可以接收這些通知和使用它在線程上做一些額外處理毛嫉。我們可以使用Core Foundation在對應的線程上注冊成為run loop的觀察者。

Run Loop Modes

Run Loop模式是一個監(jiān)視輸入源和定時器的集合和注冊成為run loop的觀察者的集合妇菱。每次要運行run loop承粤,都需要顯示或隱式地指定某種運行的mode。只有與這種指定的mode關聯的源才會被監(jiān)視和允許傳遞他們的事件闯团,同樣地辛臊,只有與這種模式關聯的觀察者都會收到run loop行為變化的通知。與其它模式想關聯的源房交,直到隨后在合適的模式通過循環(huán)后彻舰,都會接收到新的事件(比如,將timer加入run loop default模式下候味,當滾動時淹遵,timer不會收到回調,直到停止?jié)L動回到default模式下)负溪。

在我們的代碼中透揣,我們通過名稱來唯一標識mode。在Cocoa和Core Foundation中都定義了default模式和幾個常用的模式川抡,都是通過字符串名稱來指定辐真。我們也可以自定義模式须尚,但是我們需要手動添加至少一個input source/timers/observers。

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

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

Input Sources

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

Port-based:基于端口號的輸入源監(jiān)聽應用程序的Mach端口。

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

系統(tǒng)通常實現了這兩種輸入源谤牡。唯一的不同點是它們是如何被發(fā)出信號的。port-based源是由內核(kernel)自動發(fā)出信號姥宝,而custom sources必須手動從其它線程發(fā)出信號翅萤。

當我們創(chuàng)建輸入源時,可以指定mode伶授。Modes會影響任何時刻被監(jiān)視的輸入源断序。大部分情況下,我們都讓run loop在default mode下運行糜烹,但是也可以指定自定義的mode违诗。如果一個輸入源不是當前所監(jiān)視的model,它所產生的任何事件都會被保留直接進入正常的mode疮蹦。

Port-Based Sources

Cocoa和Core Foundation提供了內建支持诸迟,可以使用與port相關的對象和函數來創(chuàng)建基于端口的輸入源。舉個例子愕乎,在Cocoa中永遠不需要手動創(chuàng)建輸入源阵苇。我們只需要簡單地創(chuàng)建一個port對象和使用NSPort的方法。port對象為我們處理所需要的輸入源的創(chuàng)建和配置感论。

在Core Foundation中绅项,我們必須手動創(chuàng)建port和source。在這兩種情況下比肄,我們可以使用與port opaque type關聯的函數(CFMessagePortRef, or CFSocketRef) 來創(chuàng)建合適的對象快耿。

Custom Input Sources

在Core Foundation中囊陡,要創(chuàng)建自定義輸入源,我們必須使用與CFRunLoopSourceRef關聯的函數掀亥。我們配置自定義輸入源可以使用幾個回調函數撞反。Core Foundation會在不同點回調這些函數來配置source,處理任何到達的事件和銷毀已從run loop移除的source搪花。

除了定義在事件到達時自定義源的行為之外遏片,我們也必須定義事件傳遞機制。這部分源運行在單獨的線程撮竿,負責提供輸入源的數據吮便,當數據準備好可以處理時,signaling(通知相關線程)這個消息倚聚。事件傳遞機制是我們自己來決定线衫,但是不需要過于復雜凿可。

Cocoa Perform Selector Source

除了基于端口的源之外惑折,Cocoa還定義了自定義輸入源允許我們在任意線程上執(zhí)行selector。就像port-based源一樣枯跑,執(zhí)行selector請求會在目標線程上序列化惨驶,以減少在同一個線程中出現多個方法同步執(zhí)行的問題。與port-based源不同的是敛助,執(zhí)行selector源在執(zhí)行完畢后會自動將自己從run loop中移除粗卜。

當執(zhí)行在其它線程執(zhí)行selector時,目標線程必須要有運行的run loop纳击。當我們創(chuàng)建線程時续扔,這意味著直到啟動了run loop都會顯式地執(zhí)行selector代碼。

Run Loop每次經過一個循環(huán)焕数,就會處理隊列中所有的selector纱昧,而不僅僅是處理一個。

Timer Sources

Timer源在未來設定的時間會同步地傳遞事件到你的線程堡赔。Timers是線程通知自己去做一些事情的一種方式识脆。比如說,搜索框可以使用定時器來初始化在一定時間就自動搜索善已,以便提供更多地聯想詞給用戶灼捂。

盡管它發(fā)送基于時間的通知,但定時器并不是一種實時的機制换团。像輸入源一樣悉稠,定時器只有與run loop的mode一樣才會發(fā)送通知。如果timer在run loop中并不是所被監(jiān)視的mode艘包,它不會觸發(fā)定時器的猛,直到run loop的mode與timer所支持的mode一樣洒扎。

同樣地,如果run loop正在處理中衰絮,timer已經fire了袍冷,這時候會被中斷,直到下一次通過run loop才會調志處理程序猫牡。如果run loop已經不再運行了胡诗,則timer永遠不會再fire。

我們可以配置timer只產生事件一次或者重復產生淌友。重復的timer會自動根據調度的firing time自動調度煌恢,而不是真實的firing time。比如說震庭,如果一個timer在特定的時間調度瑰抵,然后每5秒重復一次。如果firing time被延遲導致缺少一或多次調用器联,那么timer在缺失的周期中只會調用一次二汛。

Run Loop Observers

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

進入run loop

run loop將要處理timer

run loop將要處理輸入源

run loop將要進入睡眠

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

退出run loop

我們可以通過Core Foundation來添加run loop observers婿着。要創(chuàng)建run loop observer,可以通過CFRunLoopObserverRef來創(chuàng)建新的實例。這個類型會跟蹤你所定義的回調函數和所感興趣的活動醋界。

與timers類型竟宋,run-loop observers可以使用一次或者重復多次。一次性的observer會在fire之后自動從run loop移除形纺,而重復性的observer會繼續(xù)持有丘侠。

The Run Loop Sequence Of Events

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

通知observers run loop已經進入

通知observers timers準備要fire

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

fire任何已經準備好的non-port-based輸入源

如果port-based輸入源準備好且等待fire,則立即處理這個事件官研。然后進入步驟9

通知observers線程即將進入睡眠

讓線程進入睡眠秽澳,直到以下任何一種事件到達:

port-based輸入源有事件到達

timer fire

run loop超時

run loop被顯式喚醒

通知observers線程被喚醒

處理待處理的事件:

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

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

如果run loop被顯式喚醒,但是又未超時始花,則重啟循環(huán)妄讯,進入步驟2

通知observers run loop退出

由于observer對timer和輸入源的通知會在事件真正發(fā)生之前被傳遞孩锡,這樣就產生了間隙。如果這個間隙是很關鍵的亥贸,那么我們可以通過使用sleep和awake-from-sleep通知來幫助我們糾正這個時間間隔問題躬窜。

什么時候應該使用run loop呢?

只有當我們需要創(chuàng)建子線程的時候炕置,才會需要到顯示地運行run loop荣挨。應用程序的主線程的run loop是應用啟動的基礎任務,在啟動時就會自動啟動run loop朴摊。所以我們不需要手動啟動主線程的run loop默垄。

對于子線程,我們需要確定線程是否需要run loop甚纲,如果需要口锭,則配置它并啟動它。我們并不問題需要啟動run loop的介杆。比如說鹃操,如果我們開一個子線程去執(zhí)行一些長時間的和預先決定的任務,我們可能不需要啟動run loop这溅。Run loop是用于那么需要在線程中有更多地交互的場景组民。比如說棒仍,我們會在下面的任何一種場景中需要開啟run loop:

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

在線程中使用定時器

使用Cocoa中的任何performSelector…方法

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

Using Run Loop Objects

Run Loop對象給添加輸入源悲靴、定時器和觀察者到run loop提供了主接口。每個線程都有一個單獨的run loop與之關聯(對于子線程莫其,若沒有調用過任何獲取run loop的方法是不會有run loop的癞尚,只有調用過,才會創(chuàng)建或者直接使用)乱陡。

在Cocoa中浇揩,通過NSRunLoop來創(chuàng)建實例,在low-level應用中憨颠,可以使用CFRunLoopRef類型胳徽,它是指針。

Getting A Run Loop Object

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

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

使用CFRunLoopGetCurrent()函數獲取

配置RunLoop

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

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

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

Starting the Run Loop

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

下面的幾種方式可以啟動run loop:

無條件地:無條件進入run loop是最簡單的方式,但也是最不希望這么做的税娜,因為這樣會導致run loop會進入永久地循環(huán)坐搔。可以添加敬矩、刪除輸入源和timer源概行,但是只能通過kill掉run loop才能停止。而且還不能使用自定義mode弧岳。

限時:與無條件運行run loop不同凳忙,最好是給run loop添加一個超時時間。

在特定的mode:除了添加超時時間禽炬,還可以指定mode涧卵。

Exiting the Run Loop

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

給run loop設定超時時間

告訴run loop要stop

設定超時時間是比較推薦的拙泽。我們可以通過CFRunLoopStop函數來停止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)建自定義輸入源涉及到以下部分:

想要處理的輸入源的信息

讓感興趣的客戶端知道如何聯系輸入源的調度程序

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


使輸入源失效的取消程序


二晚树、舉例說明Runloop的優(yōu)點灸拍。

一般情況下绎巨,當我們使用NSRunLoop的時候近尚,代碼如下所示:

do {

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

} while (!done);

在上面的代碼中,參數done為NO的時候场勤,當前runloop會一直接收處理其他輸入源戈锻,處理輸入源之后會再回到runloop中等待其他的輸入源;除非done為NO和媳,否則當前流程一直再runloop中格遭。

如下面的代碼片段所示,有三個按鈕窗价,分別對應如下三個action消息如庭,buttonNormalThreadTestPressed,buttonRunloopPressed,buttonTestPressed坪它。

buttonNormalThreadTestPressed:啟動一個線程骤竹,在while循環(huán)中等待線程執(zhí)行完再接著往下運行。

buttonRunloopPressed:啟動一個線程往毡,使用runloop蒙揣,等待線程執(zhí)行完再接著往下運行。

buttonTestPressed:僅僅打印兩條日志开瞭,用來測試UI是否能立即響應的懒震。

在本測試中,待程序運行后嗤详,做如下操作對比:

1个扰、點擊buttonNormalThreadTestPressed,然后立刻點擊buttonTestPressed葱色,查看日志輸出递宅。

2、待1完成后苍狰,點擊buttonRunloopPressed办龄,然后立刻點擊buttonTestPressed,查看日志輸出淋昭,跟1的日志做對比俐填,即可以發(fā)現步驟2即使線程沒有完成,在runloop等待過程中翔忽,界面仍然能夠響應英融。

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結束之前呀打,調用buttonTestPressed矢赁,界面沒有響應,直到threadProce1運行完贬丛,才打印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結束之前,調用buttonTestPressed够庙,界面立馬響應恭应,并打印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簡單實例:

- (void)viewDidLoad

{

[superviewDidLoad];

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

[NSThreaddetachNewThreadSelector:@selector(newThreadProcess)

toTarget:self

withObject:nil];

}

- (void)newThreadProcess

{

@autoreleasepool{

////獲得當前thread的Runloop

NSRunLoop* myRunLoop = [NSRunLoopcurrentRunLoop];

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

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

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

//第一個參數用于分配observer對象的內存

//第二個參數用以設置observer所要關注的事件昼榛,詳見回調函數myRunLoopObserver中注釋

//第三個參數用于標識該observer是在第一次進入runloop時執(zhí)行還是每次進入run loop處理時均執(zhí)行

//第四個參數用于設置該observer的優(yōu)先級

//第五個參數用于設置該observer的回調函數

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

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

if(observer)

{

//將Cocoa的NSRunLoop類型轉換成CoreFoundation的CFRunLoopRef類型

CFRunLoopRefcfRunLoop = [myRunLoopgetCFRunLoop];

//將新建的observer加入到當前thread的runloop

CFRunLoopAddObserver(cfRunLoop, observer,kCFRunLoopDefaultMode);

}

//

[NSTimerscheduledTimerWithTimeInterval:1

target:self

selector:@selector(timerProcess)

userInfo:nil

repeats:YES];

NSIntegerloopCount =2;

do{

//啟動當前thread的loop直到所指定的時間到達,在loop運行時剔难,runloop會處理所有來自與該run loop聯系的inputsource的數據

//對于本例與當前run loop聯系的inputsource只有一個Timer類型的source胆屿。

//該Timer每隔1秒發(fā)送觸發(fā)事件給runloop奥喻,run loop檢測到該事件時會調用相應的處理方法。

//由于在run loop添加了observer且設置observer對所有的runloop行為都感興趣非迹。

//當調用runUnitDate方法時环鲤,observer檢測到runloop啟動并進入循環(huán),observer會調用其回調函數憎兽,第二個參數所傳遞的行為是kCFRunLoopEntry冷离。

//observer檢測到runloop的其它行為并調用回調函數的操作與上面的描述相類似。

[myRunLooprunUntilDate:[NSDatedateWithTimeIntervalSinceNow:5.0]];

//當run loop的運行時間到達時纯命,會退出當前的runloop西剥。observer同樣會檢測到runloop的退出行為并調用其回調函數,第二個參數所傳遞的行為是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);

}

}

調試打印信息如下:

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.");

}

}

調試打印信息如下:

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

從調試打印信息可以看到匙铡,while循環(huán)后執(zhí)行的語句會在很長時間后才被執(zhí)行。因為碍粥,改變變量StopFlag的值鳖眼,runloop對象根本不知道,runloop在這個時候未被喚醒嚼摩。有其他事件在某個時點喚醒了主線程钦讳,這才結束了while循環(huán),但延緩的時長總是不定的枕面。愿卒。

將代碼稍微修改一下:

[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopMode

beforeDate:[NSDatedateWithTimeIntervalSinceNow:1]];

縮短runloop的休眠時間,看起來解決了上面出現的問題潮秘。

但這樣會導致runloop被經常性的喚醒琼开,違背了runloop的設計初衷。runloop的目的就死讓你的線程在有工作的時候忙于工作枕荞,而沒工作的時候處于休眠狀態(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;

}

調試打印信息如下:

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

把直接設置變量躏精,改為向主線程發(fā)送消息渣刷,喚醒runloop,延時問題解決矗烛。

參考博客1

參考博客2參考博客3

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末辅柴,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌碌嘀,老刑警劉巖涣旨,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異筏餐,居然都是意外死亡开泽,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門魁瞪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來穆律,“玉大人,你說我怎么就攤上這事导俘÷驮牛” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵旅薄,是天一觀的道長辅髓。 經常有香客問我,道長少梁,這世上最難降的妖魔是什么洛口? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮凯沪,結果婚禮上第焰,老公的妹妹穿的比我還像新娘。我一直安慰自己妨马,他們只是感情好挺举,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著烘跺,像睡著了一般湘纵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滤淳,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天梧喷,我揣著相機與錄音,去河邊找鬼娇钱。 笑死伤柄,一個胖子當著我的面吹牛,可吹牛的內容都是我干的文搂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼秤朗,長吁一口氣:“原來是場噩夢啊……” “哼煤蹭!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤硝皂,失蹤者是張志新(化名)和其女友劉穎常挚,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體稽物,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡奄毡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了贝或。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吼过。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖咪奖,靈堂內的尸體忽然破棺而出盗忱,到底是詐尸還是另有隱情,我是刑警寧澤羊赵,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布趟佃,位于F島的核電站,受9級特大地震影響昧捷,放射性物質發(fā)生泄漏闲昭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一靡挥、第九天 我趴在偏房一處隱蔽的房頂上張望序矩。 院中可真熱鬧,春花似錦芹血、人聲如沸贮泞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啃擦。三九已至,卻和暖如春饿悬,著一層夾襖步出監(jiān)牢的瞬間令蛉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工狡恬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留珠叔,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓弟劲,卻偏偏與公主長得像祷安,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子兔乞,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容