Run Loops是與線程想關(guān)聯(lián)的基礎部分。一個Run Loop就是事件處理循環(huán),它是用來調(diào)度和協(xié)調(diào)接收到的事件處理。使用Run Loop的目的,就是使得線程有工作需要做時可以忙碌起來,而當沒有事可做時,又可以使得線程睡眠。
Run Loop管理不都是自動的。我們必須手動設計線程代碼,在合適的時候來啟動Run Loop,并回應到來的事件。Cocoa和Core Foundation都提供了run loop對象來幫助我們配置和管理線程的run loop。我們的應用沒有必要顯式地創(chuàng)建這些對象;每個線程,包括應用程序的主線程,都有一個與之關(guān)聯(lián)的run loop。只有子線程才需要顯式地運行其run loop。App會將自動配置和來運行主線程的run loop的任務作為應用程序啟動處理的一部分。
對于想要更深入地了解Run Loop Objects,閱讀NSRunLoop Class Reference、CFRunLoop Reference
成都創(chuàng)新互聯(lián)是專業(yè)的豐潤網(wǎng)站建設公司,豐潤接單;提供成都網(wǎng)站建設、成都做網(wǎng)站,網(wǎng)頁設計,網(wǎng)站設計,建網(wǎng)站,PHP網(wǎng)站建設等專業(yè)做網(wǎng)站服務;采用PHP框架,可快速的進行豐潤網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團隊,希望更多企業(yè)前來合作!
Run Loop就像它的名字一樣,它使得線程進入事件循環(huán),能對到來的事件啟動事件處理。你的代碼中提供了流程控制說一句來實現(xiàn)run loop實實在在的循環(huán)部分,換句話說,你的代碼提供了while或者for循環(huán)來驅(qū)動run loop。在你的循環(huán)中,你使用run loop對象在事件到達時,運行事件處理的代碼并調(diào)起已安裝的處理程序。
Run Loop接收來自兩種不同類型的源(sources)的事件:
輸入源:異步傳遞事件,通常是來自不同的線程或不同的應用的消息。輸入源異步傳遞事件到對應的處理程序和在線程關(guān)聯(lián)的NSRunLoop對象調(diào)起runUntilDate:方法來退出事件處理。
Timer源:同步地傳遞事件,發(fā)生在每個定時器調(diào)用或周期性地調(diào)用。Timer源傳遞事件到他們的處理程序,但是不會調(diào)用run loop來退出處理。
這兩種源在事件到達時都使用應用程序特定的處理程序來處理事件。
如下圖所示,展示了run loop和不同的源的概述結(jié)構(gòu)。
p_w_picpath
除了處理輸入源之外,run loops還發(fā)出關(guān)于run loop行為的通知。我們可以注冊成為run loop的觀察者,就可以接收這些通知和使用它在線程上做一些額外處理。我們可以使用Core Foundation在對應的線程上注冊成為run loop的觀察者。
Run Loop模式是一個監(jiān)視輸入源和定時器的集合和注冊成為run loop的觀察者的集合。每次要運行run loop,都需要顯示或隱式地指定某種運行的mode。只有與這種指定的mode關(guān)聯(lián)的源才會被監(jiān)視和允許傳遞他們的事件,同樣地,只有與這種模式關(guān)聯(lián)的觀察者都會收到run loop行為變化的通知。與其它模式想關(guān)聯(lián)的源,直到隨后在合適的模式通過循環(huán)后,都會接收到新的事件(比如,將timer加入run loop default模式下,當滾動時,timer不會收到回調(diào),直到停止?jié)L動回到default模式下)。
在我們的代碼中,我們通過名稱來唯一標識mode。在Cocoa和Core Foundation中都定義了default模式和幾個常用的模式,都是通過字符串名稱來指定。我們也可以自定義模式,但是我們需要手動添加至少一個input source/timers/observers。
我們可以通過使用mode來過濾掉我們不希望接收到來自不想要的通過run loop的源。大部分情況下,我們都是使用系統(tǒng)定義的default模式。對于子線程,我們可以使用自定義模式在關(guān)鍵性操作時阻止低優(yōu)先級的源傳遞事件。
注意:Modes是通過事件源來區(qū)分,而不是事件類型來區(qū)分。比如說,我們不能使用mode來匹配只有mouse-down事件或者只有鍵盤事件。我們可以使用modes來監(jiān)聽不同系統(tǒng)的端口,臨時掛起定時器,甚至改變正在被監(jiān)視的sources和run loop觀察者。
表格.png
輸入源異步傳遞事件到你的線程。事件的源由輸入源的類型來決定,也就是兩種源中的其中一種:
Port-based:基于端口號的輸入源監(jiān)聽應用程序的Mach端口。
Custom Input Sources:自定義輸入源監(jiān)聽自定義的事件源。
系統(tǒng)通常實現(xiàn)了這兩種輸入源。唯一的不同點是它們是如何被發(fā)出信號的。port-based源是由內(nèi)核(kernel)自動發(fā)出信號,而custom sources必須手動從其它線程發(fā)出信號。
當我們創(chuàng)建輸入源時,可以指定mode。Modes會影響任何時刻被監(jiān)視的輸入源。大部分情況下,我們都讓run loop在default mode下運行,但是也可以指定自定義的mode。如果一個輸入源不是當前所監(jiān)視的model,它所產(chǎn)生的任何事件都會被保留直接進入正常的mode。
Cocoa和Core Foundation提供了內(nèi)建支持,可以使用與port相關(guān)的對象和函數(shù)來創(chuàng)建基于端口的輸入源。舉個例子,在Cocoa中永遠不需要手動創(chuàng)建輸入源。我們只需要簡單地創(chuàng)建一個port對象和使用NSPort的方法。port對象為我們處理所需要的輸入源的創(chuàng)建和配置。
在Core Foundation中,我們必須手動創(chuàng)建port和source。在這兩種情況下,我們可以使用與port opaque type關(guān)聯(lián)的函數(shù)(CFMessagePortRef, or CFSocketRef) 來創(chuàng)建合適的對象。
在Core Foundation中,要創(chuàng)建自定義輸入源,我們必須使用與CFRunLoopSourceRef關(guān)聯(lián)的函數(shù)。我們配置自定義輸入源可以使用幾個回調(diào)函數(shù)。Core Foundation會在不同點回調(diào)這些函數(shù)來配置source,處理任何到達的事件和銷毀已從run loop移除的source。
除了定義在事件到達時自定義源的行為之外,我們也必須定義事件傳遞機制。這部分源運行在單獨的線程,負責提供輸入源的數(shù)據(jù),當數(shù)據(jù)準備好可以處理時,signaling(通知相關(guān)線程)這個消息。事件傳遞機制是我們自己來決定,但是不需要過于復雜。
除了基于端口的源之外,Cocoa還定義了自定義輸入源允許我們在任意線程上執(zhí)行selector。就像port-based源一樣,執(zhí)行selector請求會在目標線程上序列化,以減少在同一個線程中出現(xiàn)多個方法同步執(zhí)行的問題。與port-based源不同的是,執(zhí)行selector源在執(zhí)行完畢后會自動將自己從run loop中移除。
當執(zhí)行在其它線程執(zhí)行selector時,目標線程必須要有運行的run loop。當我們創(chuàng)建線程時,這意味著直到啟動了run loop都會顯式地執(zhí)行selector代碼。
Run Loop每次經(jīng)過一個循環(huán),就會處理隊列中所有的selector,而不僅僅是處理一個。
方法和說明。
Timer源在未來設定的時間會同步地傳遞事件到你的線程。Timers是線程通知自己去做一些事情的一種方式。比如說,搜索框可以使用定時器來初始化在一定時間就自動搜索,以便提供更多地聯(lián)想詞給用戶。
盡管它發(fā)送基于時間的通知,但定時器并不是一種實時的機制。像輸入源一樣,定時器只有與run loop的mode一樣才會發(fā)送通知。如果timer在run loop中并不是所被監(jiān)視的mode,它不會觸發(fā)定時器,直到run loop的mode與timer所支持的mode一樣。
同樣地,如果run loop正在處理中,timer已經(jīng)fire了,這時候會被中斷,直到下一次通過run loop才會調(diào)志處理程序。如果run loop已經(jīng)不再運行了,則timer永遠不會再fire。
我們可以配置timer只產(chǎn)生事件一次或者重復產(chǎn)生。重復的timer會自動根據(jù)調(diào)度的firing time自動調(diào)度,而不是真實的firing time。比如說,如果一個timer在特定的時間調(diào)度,然后每5秒重復一次。如果firing time被延遲導致缺少一或多次調(diào)用,那么timer在缺失的周期中只會調(diào)用一次。
與sources在適當時機異步或同步發(fā)出事件不同,observers在run loop本身執(zhí)行期間,會在特定的地方發(fā)出。你可能需要到run loop observers去準備線程處理特定的事件或者在進入睡眠之前。我們可以通過以下事件來關(guān)聯(lián)run loop observers:
進入run looprun loop將要處理timer run loop將要處理輸入源 run loop將要進入睡眠 run loop被喚醒,但是還沒有處理事件 退出run loop
我們可以通過Core Foundation來添加run loop observers。要創(chuàng)建run loop observer,可以通過CFRunLoopObserverRef來創(chuàng)建新的實例。這個類型會跟蹤你所定義的回調(diào)函數(shù)和所感興趣的活動。
與timers類型,run-loop observers可以使用一次或者重復多次。一次性的observer會在fire之后自動從run loop移除,而重復性的observer會繼續(xù)持有。
本小節(jié)講的是RunLoop事件順序。每次運行它,你的線程的run loop處理待處理的事件和給所有attached observers發(fā)出通知。處理的順序如下:
1.通知observers run loop已經(jīng)進入2.通知observers timers準備要fire3.通知observers有不是基于port-based的輸入源即將要fire4.fire任何已經(jīng)準備好的non-port-based輸入源5.如果port-based輸入源準備好且等待fire,則立即處理這個事件。然后進入步驟96.通知observers線程即將進入睡眠7.讓線程進入睡眠,直到以下任何一種事件到達: * port-based輸入源有事件到達 * timer fire * run loop超時 * run loop被顯式喚醒8.通知observers線程被喚醒9.處理待處理的事件: * 如果用戶定義的timer fired了,處理timer事件并重新啟動循環(huán)。進入步驟2 * 如果輸入源fired了,則傳遞事件 * 如果run loop被顯式喚醒,但是又未超時,則重啟循環(huán),進入步驟210.通知observers run loop退出
由于observer對timer和輸入源的通知會在事件真正發(fā)生之前被傳遞,這樣就產(chǎn)生了間隙。如果這個間隙是很關(guān)鍵的,那么我們可以通過使用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í)行周期性的任務
Run Loop對象給添加輸入源、定時器和觀察者到run loop提供了主接口。每個線程都有一個單獨的run loop與之關(guān)聯(lián)(對于子線程,若沒有調(diào)用過任何獲取run loop的方法是不會有run loop的,只有調(diào)用過,才會創(chuàng)建或者直接使用)。
在Cocoa中,通過NSRunLoop來創(chuàng)建實例,在low-level應用中,可以使用CFRunLoopRef類型,它是指針。
通過以下兩種方式來獲取run loop對象:
在Cocoa中,使用[NSRunLoop currentRunLoop]獲取
使用CFRunLoopGetCurrent()函數(shù)獲取
在子線程運行run loop之前,你必須至少添加一種輸入源或者定時器。如果run loop沒有任何的源需要監(jiān)視,它就會立刻退出。
除了添加sources之外,你還可以添加觀察者來檢測runloop不同的執(zhí)行狀態(tài)。要添加觀察者,可以使用CFRunLoopObserverRef指針類型和使用CFRunLoopAddObserver函數(shù)來添加到run loop中。我們只能通過Core Foundation來創(chuàng)建run loop觀察者,即使是Cocoa應用。
下面這段代碼展示主線程如何添加觀察者到run loop以及如何創(chuàng)建run loop觀察者:
- (void)threadMain { // 應用程序使用垃圾收集,因此不需要autorelease池。 NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop]; // 創(chuàng)建一個運行循環(huán)觀察者并將它附加到運行循環(huán)。 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); } //創(chuàng)建和安排計時器。 [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(doFireTimer:) userInfo:nil repeats:YES]; NSInteger loopCount = 10; do { // 運行運行循環(huán)10次讓計時器。 [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; loopCount--; } while (loopCount); }
在為長時間存在的線程配置run-loop時,最好添加至少一個輸入源來接收事件。盡管我們可以只用timer源,但是一旦timer調(diào)用后,經(jīng)常會被invalidate,這會導致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。
下面是運行run loop的一段代碼:
- (void)skeletonThreadMain { //建立一個autorelease如果不使用垃圾收集池。 BOOL done = NO; //添加你的來源或計時器運行循環(huán),做其他任何設置。 do {/ /啟動運行循環(huán)但每個源處理后返回。 SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES); / /如果源明確停止運行循環(huán),或如果沒有源或計時器,然后退出。 if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished)) done = YES; / /檢查任何其他退出條件和設置完成所需的變量。 } while (!done); / /清理代碼。一定要釋放任何生成自動分配池。 }
有兩種方法使run loop在處理事件之前,退出run loop:
給run loop設定超時時間 告訴run loop要stop
設定超時時間是比較推薦的。我們可以通過CFRunLoopStop函數(shù)來停止run loop。
Core Foundation中的Run Loop API是線程安全的(以CF開頭的API),而Cocoa中的NSRunLoop不是線程安全的。
下面是展示如何配置不同類型的輸入源。
創(chuàng)建自定義輸入源涉及到以下部分:
想要處理的輸入源的信息
讓感興趣的客戶端知道如何聯(lián)系輸入源的調(diào)度程序
執(zhí)行任何客戶端發(fā)送的請求處理程序
使輸入源失效的取消程序
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop]; / /創(chuàng)建和安排第一個定時器。NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate interval:0.1 target:self selector:@selector(myDoFireTimer1:) userInfo:nil[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
或者使用Core Foundation:
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);
最后
本篇文章主要是官方文檔的部分翻譯版本,不過有很多無關(guān)的都省略了,而且有都轉(zhuǎn)換成筆者的語言來表達出來,如果讀不懂,最好還是去看官方文檔吧。畢竟,英文與中文翻譯不管怎么翻譯都存在很大的問題。
疑問
官方文檔中提到,每個線程都有一個run loop與之關(guān)聯(lián)。但是實質(zhì)上子線程在沒有訪問過run loop時,是不存在的。當訪問時,若不存在則創(chuàng)建run loop并放到全局數(shù)組中。
文章標題:IOS開發(fā)需要知道的知識-RunLoops
轉(zhuǎn)載源于:http://jinyejixie.com/article46/jjipeg.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站策劃、建站公司、商城網(wǎng)站、網(wǎng)站內(nèi)鏈、動態(tài)網(wǎng)站、面包屑導航
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)