When Would You Use a Run Loop?
你唯一要使用run loop,就是當你要在application中創(chuàng)建線程的時候。你Application的主線程是架構很重要的一部分。所以,iOS系統(tǒng)為app的提供了runloop代碼,并自動開始。在iOS中運行Main loop作為app啟動步驟的一部分。
對于其他的線程,你需要考慮是否必須要使用run loop。如果有需要,配置并自己啟動它。你不需要在每個線程中都啟用run loop。例如,如果你使用一個線程來執(zhí)行一些很長時間的運行和已經(jīng)準備好的任務,你可能要避免使用它。Run loop的目的主要是用來解決線程間的通信。例如,如果你打算做下面的事情,你需要開啟一個run loop。
- 使用port或者自定義input source來和其他線程通訊
- 在線程中使用timer
- 在Cocoa application使用
performSelector…方法 - 維持線程來執(zhí)行周期性的任務
如果你選擇使用run loop,配置和設置就是接下來要做的??v觀整個線程編碼過程,你應該有一個明確的認識在什么時候退出線程,這比強制退出好很多。關于如何配置和退出run loop的信息在這Using Run Loop Objects。
Using Run Loop Objects
run loop對象提供了主要的接口來添加input sources,timers,run-loop observers到run loop中再運行它。每個線程有一個和它相關的run loop object。在Cocoa中,這個對象是NSRunLoop的實例。在應用的底層,它是CFRunLoopRef類的指針。
Getting a Run Loop Object
獲取當前線程的runloop對象,你使用下面的一種方式:
- 在Cocoa Application中,使用NSRunLoop的類方法currentRunLoop,來獲取
NSRunLoop對象。 - 使用CFRunLoopGetCurrent方法。
盡管它們不是無縫橋接的類,當需要的時候你可以從NSRunLoop對象獲得一個CFRunLoopRef指針。NSRunLoop類定義了一個getCFRunLoop方法,它返回了一個CFRunLoopRef類型,這你可以傳入到Core Foundation中。因為兩個對象指向同一個run loop,需要的時候你可以混合使用。
Configuring the Run Loop
當你在自定義線程中跑run loop的時候,你至少添加一個input source或timer到run loop中,如果一個run loop沒有任何source來監(jiān)聽,當你嘗試運行它的時候,它會立即退出。如何向run loop中添加一個source,請看這里Configuring Run Loop Sources。
除了裝載sources,你也可以裝載run loop observers,使用它們來監(jiān)聽run loop的執(zhí)行步驟。為了裝載run loop observer,你創(chuàng)建了一個CFRunLoopObserverRef指針,并且使用CFRunLoopAddObserver方法并把它添加到你的run loop中。Run loop observer必須要使用Core Foundation來創(chuàng)建。
下面展示了在一個線程中,添加一個run loop observer到run loop中的主要的代碼部分。
- (void)threadMain
{
//The application use garbage collection,so no autorelease pool is needed.
NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
//Create a run loop observer and attach it to the run loop.
CFRunLoopObserverContent context = {0,self,NULL,NULL,NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&myRunLoopObserver,&context);
if(observer)
{
CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
CFRunLoopAddObserver(cfLoop,observer,kCFRunLoopDefaultMode);
}
//Create and schedule the timer.
[NSTimer scheduledTimerWithTimeInterval:0.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
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while(loopCount);
}
當為一個活躍的線程配置run loop的時候,至少添加一個input source到接受的消息。盡管你能夠只需要通過一個timer來啟動run loop中,但是,一旦timer觸發(fā)之后,它就會無效的,這會引起run loop的退出。傳入一個repeat的timer可以保持run loop運行很長一段時間。但是將會周期性的調用觸發(fā)的timer來喚醒你的線程。這是維持線程中run loop的另一種方式,一個input source等待事件的到達,讓你的線程處于睡眠中,直到它被喚醒。
Starting the Run Loop
一個run loop必須裝載至少一個input source或者timer來。如果沒有,run loop立即退出。
下面有幾種辦法開啟一個run loop。
- 無條件的啟動run loop
- 設置一個超時時間啟動run loop
- 通過一種特殊的模式啟動run loop
無條件的啟動runloop是最簡單的,但是這也是最不推薦的一種方式。無條件的啟動你的runloop,把線程作為runloop的一個參數(shù)。這種方式對runloop的控制權很小。你可以添加或移除input source和timers,唯一停止runloop的方式就是kill。這種啟動方式?jīng)]有辦法在自定義mode。
除了無條件的啟動一個runloop,使用time out value來開始一個runloop更好。當你使用time out value,runloop運行直到事件到達或者超時。如果事件抵達,事件會被派發(fā)到一個handler來處理,然后run loop退出。你的code又能夠重新開始runloop去處理下一個事件。如果超時,你可以簡單的重啟runloop,或者利用這段時間做任何需要的事。
除了time out value,你也可以使用一個指定的mode來運行runloop。Modes和timeout value并不是互斥的。當運行runloop的時候也可以同時使用它們。Modes限制了source的type。詳細被描述在Run Loop Modes。
下面的code主要部分展示了runloop的基本結構。本質上,你添加你的input source和timer到runloop。重復調用其中一個routine來開始一個run loop。每次run loop routine 返回的時,你檢查run lop是否滿足某些條件導致退出thread。例子中使用Core Foundation的run loop routines,以至于它能檢查返回的結果并檢測run loop為什么退出。你也能使用NSRunLoop的方法以一種相似的方式來運行runloop,如果你正在使用Cocoa框架中的NSRunLoop,它是不需要檢查return value的。
- (void)skeletonThreadMain {
//Set up an autorelease pool here if not using garbage collection.
BOOL done = NO;
//Add your sources or timer to the run loop and do any other setup
do
{
//Start the run loop but return after earch source is handled
SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode,10,YES)
//If a source explicitly stopped the run loop,or if there no sources or timers,go head and exit.
if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
done = YES;
//Check for any other exit conditions here and set the done variable as needed
}
while(!done);
//Clean up code here. Be sure to release any allocated autorelease pools.
}
遞歸運行run loop也是可能的。換句話說,你能夠調用CFRunLoopRun、CFRunLoopRunInMode,任何NSRunLoop的方法,從input source 或者 timer的handler routine開始runloop。當這樣做的時候,你可以使用你想要的mode來運行內嵌的runloop。包括被outer run loop正在使用的mode。
Exiting the Run Loop
有兩種辦法使runloop在處理事件之前退出。
- 為Runloop配置一個time out時間
- 主動告訴Runloop停止
使用time out時間是一種比較好的選擇。在退出之前,指定一個time out時間讓runloop完成所有的任務,包括向runloop observer發(fā)送通知。
使用CFRunLoopStop方法停止一個runloop,產(chǎn)生一個類似于超時的結果。使用runloop發(fā)送完剩下的runloop通知然后退出。唯一不同的就是只有當你無條件的啟動Runloop的時候才能使用這種方式退出。
盡管移除runloop的input source和timers可以引起runloop的退出,但是這不是一種穩(wěn)定的方式。一些系統(tǒng)一般添加input source到run loop來處理需要的事件。因為你的code不需要關心這些input source。所以你不能移除input sources,讓run loop退出。
Thread Safety and Run Loop Objects
線程安全取決于你使用操作runloop的API。在Core Foundation中的方法一般是線程安全的,可以被任何其他線程調用。如果你正在執(zhí)行操作,這些操作改變了runloop的配置。無論什么時候,這樣做任然是一個很好的實踐。
Cocoa的NSRunLoop類。Cocoa的NSRunLoop和Core Foundation不一樣,不是線程安全的。如果你使用NSRunLoop類來修改你的runloop,你應該在原來的線程中做這些操作。在其他線程添加一個input source或者timer到原來runloop可能引起crash,或者一個意想不到的行為。