RunLoop的學習

一個蘿蔔壹個坑發表於2017-12-22

什麼是Run Loops

RunLoops是與執行緒相關聯的基礎部分,一個Run Loop就是事件處理迴圈,他是用來排程和協調接收到的事件處理。使用RunLoop的目的,就是使的執行緒有工作需要做的時候忙碌起來,當沒事做的時候,又可以使得執行緒休眠。

RunLoop管理不是自動的。我們必須手動設計執行緒程式碼,在合適的時候啟動RunLoop,並回應到相應的事件。Cocoa和Core Foundation都提供了run loop物件來幫助我們配置和管理執行緒的run loop。我們的應用沒有必要顯式地建立這些物件;每個執行緒,包括應用程式的主執行緒,都有一個與之關聯的run loop。只有子執行緒才需要顯式地執行其run loop。App會將自動配置和來執行主執行緒的run loop的任務作為應用程式啟動處理的一部分。

一個RunLoop的結構

Run Loop就像它的名字一樣,它使得執行緒進入事件迴圈,能對到來的事件啟動事件處理。你的程式碼中提供了流程控制說一句來實現run loop實實在在的迴圈部分,換句話說,你的程式碼提供了while或者for迴圈來驅動run loop。在你的迴圈中,你使用run loop物件在事件到達時,執行事件處理的程式碼並調起已安裝的處理程式。

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

輸入源:非同步傳遞事件,通常是來自不同的執行緒或不同的應用的訊息。輸入源非同步傳遞事件到對應的處理程式和線上程關聯的NSRunLoop物件調起runUntilDate:方法來退出事件處理。

Timer源:同步地傳遞事件,發生在每個定時器呼叫或週期性地呼叫。Timer源傳遞事件到他們的處理程式,但是不會呼叫run loop來退出處理。

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

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

RunLoop的學習

除了處理輸入源之外,run loops還發出關於run loop行為的通知。我們可以註冊成為run loop的觀察者,就可以接收這些通知和使用它線上程上做一些額外處理。我們可以使用Core Foundation在對應的執行緒上註冊成為run loop的觀察者。

Run Loop Modes

Run Loop模式是一個監視輸入源和定時器的集合和註冊成為run loop的觀察者的集合。每次要執行run loop,都需要顯示或隱式地指定某種執行的mode。只有與這種指定的mode關聯的源才會被監視和允許傳遞他們的事件,同樣地,只有與這種模式關聯的觀察者都會收到run loop行為變化的通知。與其它模式想關聯的源,直到隨後在合適的模式通過迴圈後,都會接收到新的事件(比如,將timer加入run loop default模式下,當滾動時,timer不會收到回撥,直到停止滾動回到default模式下)。

在我們的程式碼中,我們通過名稱來唯一標識mode。在Cocoa和Core Foundation中都定義了default模式和幾個常用的模式,都是通過字串名稱來指定。我們也可以自定義模式,但是我們需要手動新增至少一個input source/timers/observers。

我們可以通過使用mode來過濾掉我們不希望接收到來自不想要的通過run loop的源。大部分情況下,我們都是使用系統定義的default模式。對於子執行緒,我們可以使用自定義模式在關鍵性操作時阻止低優先順序的源傳遞事件。

注意:Modes是通過事件源來區分,而不是事件型別來區分。比如說,我們不能使用mode來匹配只有mouse-down事件或者只有鍵盤事件。我們可以使用modes來監聽不同系統的埠,臨時掛起定時器,甚至改變正在被監視的sources和run loop觀察者。

RunLoop的學習

Input Sources

輸入源非同步傳遞事件到你的執行緒。事件的源由輸入源的型別來決定,也就是兩種源中的其中一種:

Port-based:基於埠號的輸入源監聽應用程式的Mach埠。

Custom Input Sources:自定義輸入源監聽自定義的事件源。

系統通常實現了這兩種輸入源。唯一的不同點是它們是如何被髮出訊號的。port-based源是由核心(kernel)自動發出訊號,而custom sources必須手動從其它執行緒發出訊號。

當我們建立輸入源時,可以指定mode。Modes會影響任何時刻被監視的輸入源。大部分情況下,我們都讓run loop在default mode下執行,但是也可以指定自定義的mode。如果一個輸入源不是當前所監視的model,它所產生的任何事件都會被保留直接進入正常的mode。

Port-Based Sources

Cocoa和Core Foundation提供了內建支援,可以使用與port相關的物件和函式來建立基於埠的輸入源。舉個例子,在Cocoa中永遠不需要手動建立輸入源。我們只需要簡單地建立一個port物件和使用NSPort的方法。port物件為我們處理所需要的輸入源的建立和配置。

在Core Foundation中,我們必須手動建立port和source。在這兩種情況下,我們可以使用與port opaque type關聯的函式(CFMessagePortRef, or CFSocketRef) 來建立合適的物件。

Custom Input Sources

在Core Foundation中,要建立自定義輸入源,我們必須使用與CFRunLoopSourceRef關聯的函式。我們配置自定義輸入源可以使用幾個回撥函式。Core Foundation會在不同點回撥這些函式來配置source,處理任何到達的事件和銷燬已從run loop移除的source。

除了定義在事件到達時自定義源的行為之外,我們也必須定義事件傳遞機制。這部分源執行在單獨的執行緒,負責提供輸入源的資料,當資料準備好可以處理時,signaling(通知相關執行緒)這個訊息。事件傳遞機制是我們自己來決定,但是不需要過於複雜。

Cocoa Perform Selector Source

除了基於埠的源之外,Cocoa還定義了自定義輸入源允許我們在任意執行緒上執行selector。就像port-based源一樣,執行selector請求會在目標執行緒上序列化,以減少在同一個執行緒中出現多個方法同步執行的問題。與port-based源不同的是,執行selector源在執行完畢後會自動將自己從run loop中移除。

當執行在其它執行緒執行selector時,目標執行緒必須要有執行的run loop。當我們建立執行緒時,這意味著直到啟動了run loop都會顯式地執行selector程式碼。

Run Loop每次經過一個迴圈,就會處理佇列中所有的selector,而不僅僅是處理一個。

RunLoop的學習

Timer Sources

Timer源在未來設定的時間會同步地傳遞事件到你的執行緒。Timers是執行緒通知自己去做一些事情的一種方式。比如說,搜尋框可以使用定時器來初始化在一定時間就自動搜尋,以便提供更多地聯想詞給使用者。

儘管它傳送基於時間的通知,但定時器並不是一種實時的機制。像輸入源一樣,定時器只有與run loop的mode一樣才會傳送通知。如果timer在run loop中並不是所被監視的mode,它不會觸發定時器,直到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在適當時機非同步或同步發出事件不同,observers在run loop本身執行期間,會在特定的地方發出。你可能需要到run loop observers去準備執行緒處理特定的事件或者在進入睡眠之前。我們可以通過以下事件來關聯run loop observers:

進入run loop

run loop將要處理timer

run loop將要處理輸入源

run loop將要進入睡眠

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

退出run loop

我們可以通過Core Foundation來新增run loop observers。要建立run loop observer,可以通過CFRunLoopObserverRef來建立新的例項。這個型別會跟蹤你所定義的回撥函式和所感興趣的活動。

與timers型別,run-loop observers可以使用一次或者重複多次。一次性的observer會在fire之後自動從run loop移除,而重複性的observer會繼續持有。

The Run Loop Sequence Of Events

本小節講的是RunLoop事件順序。每次執行它,你的執行緒的run loop處理待處理的事件和給所有attached observers發出通知。處理的順序如下:

通知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事件並重新啟動迴圈。進入步驟2

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

如果run loop被顯式喚醒,但是又未超時,則重啟迴圈,進入步驟2

通知observers run loop退出

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

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

只有當我們需要建立子執行緒的時候,才會需要到顯示地執行run loop。應用程式的主執行緒的run loop是應用啟動的基礎任務,在啟動時就會自動啟動run loop。所以我們不需要手動啟動主執行緒的run loop。

對於子執行緒,我們需要確定執行緒是否需要run loop,如果需要,則配置它並啟動它。我們並不問題需要啟動run loop的。比如說,如果我們開一個子執行緒去執行一些長時間的和預先決定的任務,我們可能不需要啟動run loop。Run loop是用於那麼需要線上程中有更多地互動的場景。比如說,我們會在下面的任何一種場景中需要開啟run loop:

使用埠源或者自定義輸入源與其它執行緒通訊

線上程中使用定時器

使用Cocoa中的任何performSelector…方法

保持執行緒來執行週期性的任務

Using Run Loop Objects

Run Loop物件給新增輸入源、定時器和觀察者到run loop提供了主介面。每個執行緒都有一個單獨的run loop與之關聯(對於子執行緒,若沒有呼叫過任何獲取run loop的方法是不會有run loop的,只有呼叫過,才會建立或者直接使用)。

在Cocoa中,通過NSRunLoop來建立例項,在low-level應用中,可以使用CFRunLoopRef型別,它是指標。

Getting A Run Loop Object

通過以下兩種方式來獲取run loop物件:

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

使用CFRunLoopGetCurrent()函式獲取

配置RunLoop

在子執行緒執行run loop之前,你必須至少新增一種輸入源或者定時器。如果run loop沒有任何的源需要監視,它就會立刻退出。

除了新增sources之外,你還可以新增觀察者來檢測runloop不同的執行狀態。要新增觀察者,可以使用CFRunLoopObserverRef指標型別和使用CFRunLoopAddObserver函式來新增到run loop中。我們只能通過Core Foundation來建立run loop觀察者,即使是Cocoa應用。

下面這段程式碼展示主執行緒如何新增觀察者到run loop以及如何建立run loop觀察者:

Starting the Run Loop

只有子執行緒才有可能需要啟動run loop。Run loop必須至少有一種輸入源或者timer源來監視。如果沒有任何源,則run loop會退出。

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

無條件地:無條件進入run loop是最簡單的方式,但也是最不希望這麼做的,因為這樣會導致run loop會進入永久地迴圈。可以新增、刪除輸入源和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

建立自定義輸入源涉及到以下部分:

想要處理的輸入源的資訊

讓感興趣的客戶端知道如何聯絡輸入源的排程程式

執行任何客戶端傳送的請求處理程式


使輸入源失效的取消程式


二、舉例說明Runloop的優點。

一般情況下,當我們使用NSRunLoop的時候,程式碼如下所示:

do {

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

} while (!done);

在上面的程式碼中,引數done為NO的時候,當前runloop會一直接收處理其他輸入源,處理輸入源之後會再回到runloop中等待其他的輸入源;除非done為NO,否則當前流程一直再runloop中。

如下面的程式碼片段所示,有三個按鈕,分別對應如下三個action訊息,buttonNormalThreadTestPressed,buttonRunloopPressed,buttonTestPressed。

buttonNormalThreadTestPressed:啟動一個執行緒,在while迴圈中等待執行緒執行完再接著往下執行。

buttonRunloopPressed:啟動一個執行緒,使用runloop,等待執行緒執行完再接著往下執行。

buttonTestPressed:僅僅列印兩條日誌,用來測試UI是否能立即響應的。

在本測試中,待程式執行後,做如下操作對比:

1、點選buttonNormalThreadTestPressed,然後立刻點選buttonTestPressed,檢視日誌輸出。

2、待1完成後,點選buttonRunloopPressed,然後立刻點選buttonTestPressed,檢視日誌輸出,跟1的日誌做對比,即可以發現步驟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];

// 通常等待執行緒處理完後再繼續操作的程式碼如下面的形式。

// 在等待執行緒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的執行環境

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

//建立Run loop observer物件

//第一個引數用於分配observer物件的記憶體

//第二個引數用以設定observer所要關注的事件,詳見回撥函式myRunLoopObserver中註釋

//第三個引數用於標識該observer是在第一次進入runloop時執行還是每次進入run loop處理時均執行

//第四個引數用於設定該observer的優先順序

//第五個引數用於設定該observer的回撥函式

//第六個引數用於設定該observer的執行環境

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秒傳送觸發事件給runloop,run loop檢測到該事件時會呼叫相應的處理方法。

//由於在run loop新增了observer且設定observer對所有的runloop行為都感興趣。

//當呼叫runUnitDate方法時,observer檢測到runloop啟動並進入迴圈,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可以阻塞執行緒,等待其他執行緒執行後再執行。

比如:

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迴圈後執行的語句會在很長時間後才被執行。因為,改變變數StopFlag的值,runloop物件根本不知道,runloop在這個時候未被喚醒。有其他事件在某個時點喚醒了主執行緒,這才結束了while迴圈,但延緩的時長總是不定的。。

將程式碼稍微修改一下:

[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopMode

beforeDate:[NSDatedateWithTimeIntervalSinceNow:1]];

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

但這樣會導致runloop被經常性的喚醒,違背了runloop的設計初衷。runloop的目的就死讓你的執行緒在有工作的時候忙於工作,而沒工作的時候處於休眠狀態。

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

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

把直接設定變數,改為向主執行緒傳送訊息,喚醒runloop,延時問題解決。

參考部落格1

參考部落格2參考部落格3