先貼下 apple doc, 本文基本是對照 doc 的翻譯:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
RunLoops 是 thread 線程的底層基礎(chǔ).它的本質(zhì)是和它的意思一樣運(yùn)行著的循環(huán),更加確切的說是線程中的循環(huán).它用來接受循環(huán)事件和安排線程的工作,并且在線程中沒有工作時,讓線程進(jìn)入睡眠狀態(tài).
RunLoops 并非完全自動管理的. 你可以在自己開辟的新thread 中使用runloop 來幫助你處理incoming 事件. iOS 中 Cocoa 和 CoreFoundation 框架中各有完整的一套關(guān)于 runloop 對象的操作api.在主線程中RunLoop 是系統(tǒng)創(chuàng)建的,在子線程中你必須手動去生成一個 runloop.
相關(guān)內(nèi)容: NSRunLoop CFRunLoop
什么是 RunLoops
在新建 xcode 生產(chǎn)的工程中有如下代碼塊:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([YourAppDelegate class]));
}
}
當(dāng)程序啟動時,以上代碼會被調(diào)用,主線程也隨之開始運(yùn)行,RunLoop 也會隨著啟動.
在UIApplicationMain()
方法里面完成了程序初始化,并設(shè)置程序的Delegate
任務(wù),而且隨之開啟主線程的 RunLoop,就開始接受事件處理.
RunLoop 是一個循環(huán),在里面它接受線程的輸入,通過事件處理函數(shù)來處理事件.你的代碼中應(yīng)該提供 while or for 循環(huán)來驅(qū)動 runloop.在你的循環(huán)中,用 runloop 對象驅(qū)動事件處理相關(guān)的內(nèi)容,接受事件,并做響應(yīng)的處理.
RunLoop 接受的事件源有兩種大類: 異步的input sources, 同步的 Timer sources. 這兩種事件的處理方法,系統(tǒng)所規(guī)定.
官方文檔中有如下貼圖,解釋了 runloop 的基本結(jié)構(gòu)
從圖中可以看出,RunLoop 是線程中的一個循環(huán),并且對接受到的事件進(jìn)行處理.我們的代碼可以通過提供 while 或者 for 循環(huán)來驅(qū)動 RunLoop.在循環(huán)中,Run Loop 對象來負(fù)責(zé)事件處理的代碼(接受事件,并調(diào)用相應(yīng)的事件處理方法).
RunLoop 從以下兩個不同的事件源中接受消息:
- InputSources : 用來投遞異步消息,通常消息來自另外的線程或者程序.在接受到消息并調(diào)用指定的方法時,線程對應(yīng)的 NSRunLoop 對象會通過執(zhí)行 runUntilDate:方法來退出.
- Timer Source: 用來投遞 timer 事件(Schedule 或者 Repeat)中的同步消息.在消息處理時,并不會退出 RunLoop.
- RunLoop 除了處理以上兩種 Input Soruce,它也會在運(yùn)行過程中生成不同的 notifications,標(biāo)識 runloop 所處的狀態(tài),因此可以給 RunLoop 注冊觀察者 Observer,以便監(jiān)控 RunLoop 的運(yùn)行過程,并在 RunLoop 進(jìn)入某些狀態(tài)時候進(jìn)行相應(yīng)的操作, Apple 只提供了 Core Foundation 的 API來給 RunLoop 注冊觀察者Observer.
后面兩部分內(nèi)容主要是: RunLoop 的組成, RunLoop的 modes, 以及 RunLoop 運(yùn)行過程中產(chǎn)生的 notifications
RunLoopModes-- RunLoop 運(yùn)行的模式
RunLoopMode 可以理解成為一個集合, 包括所有要監(jiān)視的事件源(前面提到的兩種源)和要通知的 RunLoop 中注冊的觀察者.每次運(yùn)行 RunLoop 時,都需要顯示或者隱式的指定其運(yùn)行在哪一種 Mode(RunLoop 每次只能運(yùn)行在一個 mode中).在設(shè)置 RunLoopMode 以后,你的 RunLoop 就會自動過濾和其他 Mode 相關(guān)的事件源,而只監(jiān)視和當(dāng)前設(shè)置 Mode 相關(guān)的源(以及通知相關(guān)的觀察者).大多數(shù)時候 RunLoop 都運(yùn)行在系統(tǒng)定義的默認(rèn)的模式上.
在代碼中,你可以通過mode 名稱區(qū)分不同的 mode. Cocoa & CoreFoundation 框架通過不同名稱(NSString,CFString)定義了缺省 mode 和一系列其他的 mode.你也可以使用不同的名稱,定義自己的 mode,然后在這個 mode 中添加一些 source 以及 observer.
使用這些 modes 可以從不想要的事件源中過濾事件.大多數(shù)情況下,我們都將 runloop 設(shè)置成default mode.
RunLoopMode 是基于事件的source源頭,而不是事件的type類型去區(qū)分的.比如你不能通過 RunLoopMode 去只選擇鼠標(biāo)點(diǎn)擊事件或者鍵盤輸入事件.你可以使用 RunLoopMode 去監(jiān)聽端口,暫停計(jì)時器或者或者改變添加或刪除一些 mode 中關(guān)注的 sources or observers.
Cocoa 和 CoreFoundation 為我們定義了默認(rèn)和常用的 Mode.RunLoopMode 的名稱可以使用字符串來標(biāo)識,我們也可以使用字符串指定一個 Mode 名稱來自定義 Model.
下面列出iOS 中已經(jīng)定義的 RunLoopMode:
- NSDefaultRunLoopMode,kCFRunLoopDefaultMode: 大多數(shù)工作中默認(rèn)的運(yùn)行方式。
- NSConnectionReplyMode: 使用這個Mode去監(jiān)聽NSConnection對象的狀態(tài),我們很少需要自己使用這個Mode。
- NSModalPanelRunLoopMode: 使用這個Mode在Model Panel情況下去區(qū)分事件(OS X開發(fā)中會遇到)。
- UITrackingRunLoopMode: 使用這個Mode去跟蹤來自用戶交互的事件(比如UITableView上下滑動)。
- GSEventReceiveRunLoopMode: 用來接受系統(tǒng)事件抚太,內(nèi)部的Run Loop Mode郊楣。
- NSRunLoopCommonModes, kCFRunLoopCommomModes: 這是一個偽模式操刀,其為一組run loop mode的集合囊扳。如果將Input source加入此模式,意味著關(guān)聯(lián)Input source到Common Modes中包含的所有模式下兜看。在iOS系統(tǒng)中NSRunLoopCommonMode包含NSDefaultRunLoopMode锥咸、NSTaskDeathCheckMode、UITrackingRunLoopMode.同時,我們可以使用 CoreFoundation 中的 CFRunLoopAddCommomMode()函數(shù),將自定義的 mode 加入其中细移。
注意 RunLoop 運(yùn)行時只能以一種固定的 Mode運(yùn)行,只會監(jiān)控這個 Mode 下添加的 Timer source 和 Input source.如果這個 Mode下沒有添加時間源,RunLoop 就會立即返回.
RunLoop 不能運(yùn)行在 NSRunLoopCommonModes,因?yàn)?NSRunLoopModes 是個 Mode 的集合,而不是一個具體的 Mode.我們可以在添加事件源的時候使用 NSRunLoopCommomModes,只要 RunLoop 運(yùn)行在 NSRunLoopModes 中任何一個 Mode,這個事件源就會被觸發(fā).
RunLoop 的事件源
Input soruces 將 event 異步的發(fā)送給 threads.而event 的 source 是基于input source 的類型.Input source 有兩種不同的種類: Port-Based Sources
和 Custom Input sources
. Port-Based Sources
監(jiān)聽 Mach Port ,Custom Input Source
類型 監(jiān)控著 custom source 發(fā)出的 event.這兩種不同類型的 Input source 區(qū)別在于: Port-Based Sources
由內(nèi)核自動發(fā)送,custom sources 必須手動的從其他 thread 發(fā)出
當(dāng)你創(chuàng)建一個 input soruce, 你需要將它放入到一個或者多個 runloop modes 中.如果一個 input source 不在當(dāng)前的被監(jiān)控的 mode 中, 那么這個 input source 產(chǎn)生的事件 Runloop 是不會收到的,除非 這個 input source 被放到了正確的 mode 中.
下文描述了幾種不同的 input sources.
Port-Based Sources
Cocoa 和 CoreFoundation 框架 對 port-based
input soruces 相關(guān)的對象和函數(shù)提供了內(nèi)置的支持.比如Cocoa 中,你永遠(yuǎn)不必直接創(chuàng)建一個 input source,你可以直接通過方法創(chuàng)建一個 NSPort,然后直接將這個 port 對象加入到 runloop 中. NSPort 對象會負(fù)責(zé)自己創(chuàng)建和配置 input source.
在CoreFoundation 中,你必須手動創(chuàng)建 port 和與它對應(yīng)的 runloop source.使用 CFMachPortRef,CFMessagePortRef,CFSocketRef 去創(chuàng)建合適的對象.
具體的創(chuàng)建 port-based soruce 的實(shí)例見:
Custom Input Source
我們可以使用 CoreFoundation 中的 CFRunLoopSourceRef 類型相關(guān)的函數(shù)來創(chuàng)建 custom input sources. 你可以使用幾個 callback 函數(shù)來配置 custom input source. CoreFoundation 會在幾個不同的事件觸發(fā)的節(jié)點(diǎn)來調(diào)用注冊的 callback 函數(shù),具體的節(jié)點(diǎn): configuration, handle incoming event以及銷毀 source.
同時,你必須定義指定當(dāng) event 到來時,custom source的行為,以及事件的傳遞的形式.
具體的調(diào)用實(shí)例:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW3
Cocoa Perform Selector Sources
Cocoa 框架為我們定義了一些 Custom Input Sources, 允許我們在任何thread中執(zhí)行Selector
方法.
當(dāng)在一個目標(biāo) thread 中 perform selector,需要該 thread 的 runloop 必須是運(yùn)行狀態(tài)的.這意味著,如果這個 thread 是你創(chuàng)建的,那么 selector 的內(nèi)容直到 runloop 啟動以后才會執(zhí)行,而之前通過 perform selector 加入的就會被加入到一個queue中等待執(zhí)行.和Port-Based Sources
一樣,這些 selector 的請求會在目標(biāo)線程中加入到 queue 中序列化,以減緩線程中多個方法執(zhí)行帶來的同步問題.
和Port-Based Sources
不一樣的是,一個 selector 方法執(zhí)行完成以后會自動從當(dāng)前RunLoop 中移除.
下面是 NSObject 中定義的 perform selector 的方法
//在主線程的 RunLoop 下執(zhí)行指定的 @selector 方法
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
//在當(dāng)前線程的 RunLoop 下延遲加載指定的 @selector 方法
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
//在當(dāng)前線程的 RunLoop 下延遲加載執(zhí)行的 @selector 方法
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
//取消當(dāng)前線程的調(diào)用
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:
Timer sources
Timer sources在預(yù)設(shè)的時間點(diǎn)synchronously
同步的給 thread發(fā)送 evet.Timer 是thread 通知自己做某事的一種方式. 例如, search filed 可以使用一個 timer 來每隔一段時間自動去查詢用戶輸入的信息.
盡管, Timer sources 會生成 time-based notifications, 但是 timer 并非完全真正的基于時間點(diǎn)的.同 input source 一樣, Timer 也是和特定的RunLoopMode 有關(guān)的.如果Timer并不在當(dāng)前 RunLoop 的 mode 的監(jiān)控范圍內(nèi),那么 Timer 就不會被觸發(fā),直到 RunLoop 切換到 Timer 所在的 mode 中.相似的是,如果 Timer 在當(dāng)前 mode 觸發(fā)了,但是 RunLoopMode 又被改變了,那么后面 Timer 就仍然不會被觸發(fā).
我們可以設(shè)置 Timer 的觸發(fā)方式是once 一次性 或者 repeat 重復(fù). 一個 repeating timer 重復(fù)觸發(fā)依賴的時間是基于上一次的 fire time 的,并非實(shí)際的時間(有可能中間 runloopmode 有改變).例如, 一個 timer 設(shè)定的觸發(fā)時間是每隔5s 觸發(fā)一次,那么每一次激活都是前一次再加5s,即使前一次 fire 時間有 delay.即使有一次 fire time 延遲了很久,可能是13s,錯過了兩次 fire time,那么后面仍然只 fire 一次,在timer 這次 fire 過后, timer 又重新規(guī)劃下次 fire 時間在5s 后.
Cocoa 和 CoreFoundation 中 NSTimer,CFRunLoopTimer 提供了相關(guān)方法來設(shè)置 Timer sources.需要注意的是除了scheduledTimerWithTimeInterval
開頭的方法創(chuàng)建的 Timer 都需要手動添加到當(dāng)前RunLoop中.(scheduledTimerWithTimeInterval 創(chuàng)建的 timer 會自動以 default mode 加載到當(dāng)前 RunLoop 中)
Timer 在選擇使用一次后,在執(zhí)行完成時,會從 RunLoop 中移除.選擇循環(huán)時候,會一直保存在當(dāng)前 RunLoop 中,直到 invalidated 方法執(zhí)行.
具體使用 timer 的實(shí)例:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW6
RunLoop Observers, RunLoop 運(yùn)行過程中的狀態(tài)觀察者
上文中提到的激活 RunLoop 的 input source 事件源, 他們會產(chǎn)生 同步(synchronous "timer")或者異步(asynchronous custom source等),與之對應(yīng), runloop 的 observers 將會在 runloop 運(yùn)行到某個狀態(tài)時候自動觸發(fā). 你可以在 runloop 即將處理某個事件時使用observers 進(jìn)行某項(xiàng)操作,或者使用 observers 對 runloop 進(jìn)入睡眠時候去控制 thread. 簡而言之, RunLoop Observer 則在 RunLoop 本身進(jìn)入某個狀態(tài)時候得到通知
你可以創(chuàng)建一個 Observer 對 RunLoop 的如下狀態(tài)進(jìn)行觀察:
- RunLoop 進(jìn)入時候
- RunLoop 將要處理一個 Timer 時候
- RunLoop 將要處理一個 Input Source時候
- RunLoop 將要進(jìn)入睡眠的時候
- RunLoop 將要被喚醒的時候,在喚醒它的事件被處理之前
- RunLoop 停止的時候
你可以用 CoreFoundation 的 API 給 RunLoop 添加 observers.使用CFRunLoopObserverRef 創(chuàng)建一個 runloop observer 的實(shí)例. 在創(chuàng)建實(shí)例時候, 給 observer 配置 callbacks 來跟蹤 runloop的狀態(tài).
與 timer 類似, runloop observers 可以被聲明成 once 一次 或者 repeat 重復(fù)監(jiān)控. 一次性的 observer 會在它被觸發(fā),完成相關(guān)操作就從 runloop 中移除監(jiān)控, repeat 類型還會繼續(xù)監(jiān)聽. 當(dāng)你創(chuàng)建一個 observers 你就應(yīng)該指定是once 還是 repeat.
RunLoop 事件的序列
每次, RunLoop 會操作輸入的 events(各種 input source 產(chǎn)生的) 并產(chǎn)生 notifications(給 observers).
RunLoop 的處理 events 并且發(fā)出 notifications 的具體的順序如下:
1> 發(fā) notification,通知 Observer,當(dāng) RunLoop 開始進(jìn)入循環(huán)的時候
2> 發(fā) notification,通知 Observer, timers 即將被觸發(fā)(處理 timers 的event)
3> 發(fā) notification,通知 Observer,有其他的非 Port-Based Input Source 即將被觸發(fā)(non-port-based input source 的 event)
4> 啟動非 Port-Based Input Soruce 的事件源的處理函數(shù)
5> 如果基于Port的Input Source事件源即將觸發(fā)時搏予,立即處理該事件,并跳轉(zhuǎn)到9
6> 發(fā) notification,通知 Observer,當(dāng)前 thread 即將進(jìn)入睡眠狀態(tài)
7> 使線程進(jìn)入睡眠狀態(tài)直到有以下事件發(fā)生:
1.Port-Based Input Source event
2.Timer fires
3.RunLoop 設(shè)置的時間超時
4.RunLoop 被代碼顯示喚醒
8> 發(fā) notification,通知 Observer, thread將要被喚醒
9> 處理被觸發(fā)的事件
1.如果是用戶自定義的Timer觸發(fā)的,處理Timer事件的函數(shù)后,重啟Run Loop.直接進(jìn)入步驟2
2.如果是 input source 事件源有事觸發(fā) event弧轧,直接傳遞這個消息
3.如果runloop 是顯示被喚醒并且沒有超時,重啟 RunLoop. 直接進(jìn)入步驟2
10> 發(fā) notification,通知 Observer雪侥,Run Loop已經(jīng)退出
由于 timer 和 input source導(dǎo)致 runloop 給 observer 發(fā)送 notification 是在這些事件之前的, 因此可能 notification 發(fā)出以后到事件真正的發(fā)生中間會有一小段間隔時間, 可以通過 awake-from-sleep 的 notification 來修復(fù)確切的時間.
因?yàn)?timers 和其他與時間片相關(guān)的事件會在runloop 運(yùn)行時候傳遞, 有時候會使得循環(huán)傳遞這些事件失靈.典型例子就是, 你在進(jìn)入 runloop 時候,監(jiān)聽了鼠標(biāo)的移動路線,然后不斷在 app 中請求一些事件.因?yàn)槟愕拇a中已經(jīng)抓住這些事件,并直接處理了,而不是讓 app 正常的去 dispatch事件,活動著的 timers 講將不能被fire,直到你的鼠標(biāo)追蹤事件停止,并把控制權(quán)交給 app.
RunLoop 可以使用 runLoop 對象顯示的喚醒. 其他的事件也能夠喚醒 runloop.例如,給 runloop 添加一個 non-port-based input source,就可以喚醒它,而不必等到其他的事件發(fā)生.
何時使用 RunLoop
我們應(yīng)該只在創(chuàng)建輔助線程的時候,才顯示的運(yùn)行一個 RunLoop.iOS app 會在應(yīng)用啟動的時候幫我 run 一個 runloop,而我們自己新建的輔助線程不會.
對于輔助線程,我們?nèi)匀恍枰袛嗍欠裥枰獑右粋€ RunLoop.比如我們使用一個線程去處理一個預(yù)先定義的長時間的任務(wù),我們應(yīng)該避免啟動 RunLoop.下面是官方document 提供的使用 RunLoop 的幾個場景:
- 需要使用 Port-Based Input Source或者 Custom InputSource 和其他thread通訊時
- 需要在線程中使用 Timer
- 需要在線程中使用上文中提到的
selector
相關(guān)的方法 - 需要讓線程周期性的執(zhí)行某些工作
如果你選擇使用 RunLoop, runloop 的設(shè)置和啟動是比較直觀的. 同時,你需要實(shí)現(xiàn)什么情況下從輔助線程中退出 runloop, 最好不要直接關(guān)閉線程,而是先退出 runloop.
如何創(chuàng)建和設(shè)置 runloop.代碼:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW5
使用 RunLoop 對象
RunLoop 對系為增加 input source,timers,添加 observers 提供了主要的接口.每一個 thread 都有且僅有一個 runloop.Cocoa 中使用 NSRunLoop, CoreFoundation 中使用 CFRunLoopRef.
從線程中獲取 RunLoop 對象
為了從當(dāng)前 thread 中獲取runloop 對象,具體步驟如下:
- 在 Cocoa中, 使用 [NSRunLoop currentRunLoop] ,就會返回當(dāng)前線程的 runLoop 對象.
- CoreFoundation 中使用 CFRunLoopRef.
CFRunLoopRef和 NSRunLoop 可以轉(zhuǎn)化, NSRunLoop 使用getCFRunLoop
方法就可以得到 CFRunLoopRef 對象
配置 RunLoop 對象
在輔助線程啟動 runloop 之前,你必須至少在其中添加一個 input source 或者 timer.如果一個 runloop 中沒有一個事件源sources, runloop 會在你啟動它以后立即退出.
在添加了 source 以后,你可以給 runloop 添加 observers 來監(jiān)測 runloop 的不同的執(zhí)行的狀態(tài).為了加入 observer, 你應(yīng)該創(chuàng)建一個 CFRunLoopObserverRef,使用 CFRunLoopAddObserver 函數(shù)添加 observer 到你的 runloop.
下面代碼塊顯示了, 如何給 RunLoop 添加一個 observer .
- (void)threadMain
{
// The application uses garbage collection, so no autorelease pool is needed.
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create a run loop observer and attach it to the run loop.
CFRunLoopObserverContext 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);
}
啟動 RunLoop
在輔助線程中,啟動一個 runloop 是必須的.runloop 中必須有一個 input source 或者 timer事件源.如果 runloop 啟動時候,內(nèi)部沒有監(jiān)聽任何一個 input source 或者 timer, runloop 會立即 exit.
有以下幾種啟動 RunLoop 的方法:
- 沒有設(shè)置特定的條件啟動
- 設(shè)置一個時間限制
- 在一個特定的 mode
最簡單的是一個無條件的啟動 runloop,但是這也是最差的選擇.如果沒有設(shè)置任何條件,就會將 runloop 所在的 thread 進(jìn)行永久的事件循環(huán).你可以增加或者減少 input sources, timers,但是只有一種方法能夠 kill 掉它.同時這種方式?jīng)]辦法讓 runloop 在自定義的 mode 中運(yùn)行.
替代無條件進(jìn)入run loop更好的辦法是用預(yù)設(shè)超時時間來運(yùn)行runloop,這樣runloop運(yùn)作直到某一事件到達(dá)或者規(guī)定的時間已經(jīng)到期精绎。如果是事件到達(dá)速缨,消息會被傳遞給相應(yīng)的處理程序來處理,然后runloop退出代乃。你可以重新啟動runloop來等待下一事件旬牲。如果是規(guī)定時間到期了,你只需簡單的重啟runloop或使用此段時間來做任何的其他工作搁吓。
除了超時機(jī)制原茅,你也可以使用特定的模式來運(yùn)行你的runloop。模式和超時不是互斥的堕仔,他們可以在啟動runloop的時候同時使用擂橘。模式限制了可以傳遞事件給run loop的輸入源的類型,這在”Run Loop模式”部分介紹摩骨。
描述了線程的主要例程的架構(gòu)通贞。本示例的關(guān)鍵是說明了runloop的基本結(jié)構(gòu)。本質(zhì)上講你添加自己的輸入源或定時器到runloop里面仿吞,然后重復(fù)的調(diào)用一個程序來啟動runloop滑频。每次runloop返回的時候,你需要檢查是否有使線程退出的條件成立唤冈。示例中使用了Core Foundation的run loop例程峡迷,以便可以檢查返回結(jié)果從而確定run loop為何退出。若是在Cocoa程序,你也可以使用NSRunLoop 的方法運(yùn)行run loop绘搞,無需檢查返回值彤避。
- (void)threadMain
{
// The application uses garbage collection, so no autorelease pool is needed.
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create a run loop observer and attach it to the run loop.
CFRunLoopObserverContext 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);
}
可以遞歸的運(yùn)行run loop。換句話說你可以使用CFRunLoopRun夯辖,CFRunLoopRunInMode或者任一NSRunLoop的方法在輸入源或定時器的處理程序里面啟動run loop琉预。這樣做的話,你可以使用任何模式啟動嵌套的run loop蒿褂,包括被外層run loop使用的模式圆米。
退出 RunLoop
有兩種方法可以讓 runloop 退出:
- 給 runloop 設(shè)置超時事件
- 通知 runloop 停止
如果可以配置的話,推薦使用第一種方法啄栓。指定一個超時時間可以使run loop退出前完成所有正常操作娄帖,包括發(fā)送消息給run loop觀察者。
使用CFRunLoopStop來顯式的停止runloop和使用超時時間產(chǎn)生的結(jié)果相似昙楚。Runloop把所有剩余的通知發(fā)送出去再退出近速。與設(shè)置超時的不同的是你可以在無條件啟動的run loop里面使用該技術(shù)。
盡管移除runloop的輸入源和定時器也可能導(dǎo)致run loop退出堪旧,但這并不是可靠的退出run loop的方法削葱。一些系統(tǒng)例程會添加輸入源到runloop里面來處理所需事件。因?yàn)槟愕拇a未必會考慮到這些輸入源淳梦,這樣可能導(dǎo)致你無法沒從系統(tǒng)例程中移除它們析砸,從而導(dǎo)致退出runloop。
線程安全和 RunLoop 對象
線程是否安全取決于你使用那些API來操縱你的runloop爆袍。CoreFoundation 中的函數(shù)通常是線程安全的干厚,可以被任意線程調(diào)用。但是如果你修改了runloop的配置然后需要執(zhí)行某些操作螃宙,任何時候你最好還是在run loop所屬的線程執(zhí)行這些操作蛮瞄。
至于Cocoa的NSRunLoop類則不像CoreFoundation具有與生俱來的線程安全性。如果你想使用NSRunLoop類來修改你的runloop谆扎,你應(yīng)用在runloop所屬的線程里面完成這些操作挂捅。給屬于不同線程的runloop添加輸入源和定時器有可能導(dǎo)致你的代碼崩潰或產(chǎn)生不可預(yù)知的行為。(不要在當(dāng)前線程操作其他線程的 runloop)
配置 RunLoop sources
以下部分列舉了在Cocoa和Core Foundation里面如何設(shè)置不同類型的輸入源的例子
自定義的 input sources
創(chuàng)建自定義的輸入源包括定義以下內(nèi)容:
- 輸入源需要處理的信息
- 使感興趣的客戶端(可理解為其他線程)知道如何和輸入源交互的調(diào)度程序
- 處理其他任何客戶端(可理解為其他線程)發(fā)送請求的程序
- 使輸入源失效的取消程序
由于你自己創(chuàng)建輸入源來處理自定義消息堂湖,實(shí)際配置選是靈活配置的闲先。調(diào)度程序,處理程序和取消程序都是你創(chuàng)建自定義輸入源時最關(guān)鍵的例程无蜂。然而輸入源其他的大部分行為都發(fā)生在這些例程的外部伺糠。比如,由你決定數(shù)據(jù)傳輸?shù)捷斎朐吹臋C(jī)制斥季,還有輸入源和其他線程的通信機(jī)制也是由你決定训桶。
ps: custom 源很少用...具體見
http://www.dreamingwish.com/article/ios-multithread-program-runloop-the.html
配置Timer source
為了創(chuàng)建一個定時源累驮,你所需要做只是創(chuàng)建一個定時器對象并把它調(diào)度到你的runloop。Cocoa程序中使用NSTimer類來創(chuàng)建一個新的定時器對象舵揭,而Core Foundation中使用CFRunLoopTimerRef不透明類型谤专。本質(zhì)上,NSTimer類是CoreFoundation的簡單擴(kuò)展午绳,它提供了便利的特征置侍,例如能使用相同的方法創(chuàng)建和調(diào)配定時器。
Cocoa中可以使用以下NSTimer類方法來創(chuàng)建并調(diào)配一個定時器:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
scheduledTimerWithTimeInterval:invocation:repeats:
上述方法創(chuàng)建了定時器并以默認(rèn)模式把它們添加到當(dāng)前線程的run loop拦焚。你可以手工的創(chuàng)建NSTimer對象蜡坊,并通過NSRunLoop的addTimer:forMode:把它添加到run loop。兩種方法都做了相同的事赎败,區(qū)別在于你對定時器配置的控制權(quán)算色。例如,如果你手工創(chuàng)建定時器并把它添加到run loop螟够,你可以選擇要添加的模式而不使用默認(rèn)模式。下面的嗲嗎顯示了如何使用這這兩種方法創(chuàng)建定時器峡钓。第一個定時器在初始化后1秒開始運(yùn)行妓笙,此后每隔0.1秒運(yùn)行。第二個定時器則在初始化后0.2秒開始運(yùn)行能岩,此后每隔0.2秒運(yùn)行寞宫。
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create and schedule the first timer.
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
interval:0.1
target:self
selector:@selector(myDoFireTimer1:)
userInfo:nil
repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
// Create and schedule the second timer.
[NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:@selector(myDoFireTimer2:)
userInfo:nil
repeats:YES];
下面的代碼顯示了使用Core Foundation函數(shù)來配置定時器的代碼。盡管這個例子中并沒有把任何用戶定義的信息作為上下文結(jié)構(gòu)拉鹃,但是你可以使用這個上下文結(jié)構(gòu)傳遞任何你想傳遞的信息給定時器辈赋。關(guān)于該上下文結(jié)構(gòu)的內(nèi)容的詳細(xì)信息,參閱CFRunLoopTimer Reference膏燕。
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,
&myCFTimerCallback, &context);
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);
配置基于 port-based source
Cocoa和Core Foundation都提供了基于端口的對象用于線程或進(jìn)程間的通信钥屈。以下部分顯示如何使用幾種不同類型的端口對象建立端口通信。
配置NSMachPort對象
為了和NSMachPort對象建立穩(wěn)定的本地連接坝辫,你需要創(chuàng)建端口對象并將之加入相應(yīng)的線程的run loop篷就。當(dāng)運(yùn)行輔助線程的時候,你傳遞端口對象到線程的主體入口點(diǎn)近忙。輔助線程可以使用相同的端口對象將消息返回給原線程竭业。
ps: 實(shí)際進(jìn)程間通信用的比較少,AFNetworking 2.x里面有使用.防止 runloop 停止,在啟動 runloop 之前就添加了一個 NSPort source.
參考文檔:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
http://chun.tips/blog/2014/10/20/zou-jin-run-loopde-shi-jie-%5B%3F%5D-:shi-yao-shi-run-loop%3F/
http://www.dreamingwish.com/frontui/article/default/ios-multithread-program-runloop-the.html
https://github.com/wuyunfeng/LightWeightRunLoop
https://github.com/yechunjun/RunLoopDemo
http://blog.ibireme.com/2015/05/18/runloop/