運行的循環是與線程相關聯的基本基礎結構的一部分。運行循環是您使用調度工作和協調收到傳入的事件的事件處理循環。運行循環的目的是保持您的線程忙工作、 並把您的線程睡眠時沒有的時候。
運行的循環管理不是完全自動的。您仍然必須設計您的線程代碼以在適當的時候啟動運行的循環和響應傳入的事件。可可和核心基礎提供運行循環對象以幫助您配置和管理您的線程運行的循環。您的應用程序不會創建這些對象明確 ;每個線程,包括應用程序的主線程有一個關聯的運行的循環對象。只有第二個線程需要明確、 但是運行其運行的循環。應用程序框架自動設置,並在主線程上運行運行的循環作為應用程序啟動過程的一部分。
以下各節提供有關運行的循環和你如何將它們配置為您的應用程序的詳細信息。有關運行的循環對象的其他信息請參閱NSRunLoop 類引用和CFRunLoop 參考.
一個運行的循環是很像它的名字聽起來。這是一個循環、 您的線程進入和使用來運行以響應傳入事件的事件處理程序。您的代碼提供了用於執行運行回路的實際循環部分的控制語句 — — — — 換句話說,您的代碼提供驅動器運行的循環的你努力放松一下或為循環。在你的循環中、 您使用運行的循環對象運行接收事件並調用安裝處理程序的事件處理代碼。
一個運行的循環從兩個不同類型的源接收事件。輸入源提供異步事件、 通常從另一個線程或不同的應用程序的消息。計時器來源提供同步的事件、 發生在預定的時間或重復時間間隔。兩種類型的源使用特定於應用程序處理程序例程來處理的事件、 當它到達。
圖 3-1顯示了一個運行的循環和各種來源的概念結構。輸入的源提供異步事件到對應的處理程序並導致runUntilDate: (在該線程關聯的NSRunLoop對象上調用) 方法來退出。計時器來源到其處理程序例程傳遞事件,但不是會導致循環的運行退出。
除了處理輸入源、 運行的循環也產生運行的循環行為有關的通知。注冊的運行循環觀察員可以接收這些通知、 並用於執行附加處理的線程上。您可以使用核心基礎在您的線程上安裝運行循環觀察員。
以下各節提供有關組件運行的循環和它們在其中運作的模式的詳細信息。他們還描述了在不同的時間、 期間處理的事件生成的通知。
運行循環模式是輸入的源和要監視的計時器的集合和集合運行的循環觀察員通知。每次您運行的循環運行時、 您指定 (顯式或隱式) 要在其中運行一個特定的模式。在這階段運行循環中、 與該模式相關聯的唯一來源監察及允許提供他們的活動。(同樣與該模式相關聯的唯一觀察員的通知運行的循環進展。)與其他模式關聯的源直到後來經過適當的模式中的循環都堅持到任何新的事件。
在您的代碼中、 您按名稱標識模式。可可和核心基礎定義默認模式和幾種常用的模式、 還會在您的代碼中指定這些模式的字符串。只需指定一個自定義的字符串的模式名稱、 您可以定義自定義模式。雖然您將分配給自定義的模式的名稱是任意的這些模式的內容並不。你一定要添加一個或多個輸入源、 計時器或為他們創建的任何模式運行循環觀察員要有用。
您可以使用模式特定傳遞期間通過您運行的循環過濾掉來自意外源的事件。大多數時候、 你會想要在系統定義默認值模式下運行運行的循環。模態的面板中,但是,可能在模式模式下運行。而在此模式下、 只有源有關的模式面板將事件線程。對於第二個線程、 您可以使用自定義的模式以防止低優先級的內容源將事件傳遞在時間關鍵型操作期間。
注:模式歧視事件、 不是類型的事件的源上的基礎。不會使用模式來匹配只有鼠標按下事件或只有鍵盤事件。 後點查詢即可或否則為更改源和運行當前正在監視的循環觀察員。 您可以使用模式來聽一組不同的端口、 暫停計時器
文本 3 1列出了當您使用該模式時、 由可可和核心基礎以及的描述定義的標准模式。名稱列中列出了實際的常量您使用您的代碼中指定的模式。
模式
名稱
可選項
默認
NSDefaultRunLoopMode (可可)
kCFRunLoopDefaultMode () 核心基金會
默認模式是用於大多數操作。大多數情況下、 應使用此模式來開始您運行的循環和配置輸入的源。
連接
NSConnectionReplyMode(可可)
可可將與結合使用這種模式NSConnection要監視的答復的對象。你應該很少需要使用這種模式下你自己。
模態
NSModalPanelRunLoopMode(可可)
可可使用此模式來識別活動、 目的是為模態的面板。
事件跟蹤
NSEventTrackingRunLoopMode(可可)
可可使用此模式來限制傳入的事件期間鼠標拖動的循環和其他各種用戶界面跟蹤環。
常見模式
NSRunLoopCommonModes (可可)
kCFRunLoopCommonModes () 核心基金會
這是常用的模式可配置組。將輸入的源與此模式相關聯、 與每個組中的模式也關聯。對於可可應用程序、 這套包括違約、 莫代爾、 和事件默認跟蹤模式。核心基礎包括最初只是默認的模式。您可以使用CFRunLoopAddCommonMode函數集添加自定義模式。
輸入的源到您的線程異步發送事件。事件源取決於輸入的源、 通常是兩個類別之一的類型。基於端口的輸入的源監視您的應用程序的馬赫端口。自定義輸入的源監視事件的自定義源。您運行的循環而言它應該不管輸入的源是基於端口或自定義。該系統通常實現是您可以使用這兩種類型的輸入的的源。這兩個來源之間的唯一區別是如何、 他們都已終止。基於端口的源都由內核,自動終止和自定義源必須用手動信號通知從另一個線程。
當你創建一個輸入的源時、 您可以將其分配給一個或多個您運行的循環模式。模式影響的輸入的源監測在任何給定的時刻。大多數情況下、 在默認模式下、 運行在運行的循環、 但您也可以指定自定義模式。如果輸入的源不在當前被監視模式下,它會生成任何事件舉行直到運行的循環運行在正確的模式。
以下各節描述一些輸入源。
可可和核心基礎用於創建基於端口的輸入的源使用端口相關的對象和函數提供內置支持。後點查詢即可,可可您不需要直接在所有創建的輸入的源。你只需創建端口對象、 並使用NSPort方法將該端口添加到運行循環。端口對象處理的創建和配置所需的輸入源中。
在核心基礎、 您必須手動創建端口和其運行的循環源。在這兩種情況下、 您使用與端口不透明類型 (CFMachPortRef、 CFMessagePortRef或CFSocketRef) 來創建相應的對象相關聯的功能。
有關如何設置和配置自定義的基於端口的源的示例、 請參閱配置基於端口的輸入源.
若要創建自定義的輸入的源、 您必須使用與核心基礎中的CFRunLoopSourceRef不透明類型相關聯的函數。您配置自定義的輸入的源、 使用幾個回調函數。核心基礎在不同的點來配置源、 處理任何傳入的事件,以及拆除源它從運行循環中移除時調用這些函數。
除了定義的行為的自定義源,當事件到達時您還必須定義事件交付機制。這部分源在一個單獨的線程上運行,並負責提供其數據的輸入的源並准備好處理這些數據時向其發出信號。事件傳遞機制是取決於你、 但不是需要過於復雜。
有關如何創建自定義的輸入的源的示例、 請參閱定義自定義的輸入源。自定義輸入源的參考信息、 請參閱CFRunLoopSource 參考.
除了基於端口的來源,可可定義自定義的輸入的源使您可以在任何線程上執行一個選擇器。像一個基於端口的源,在目標線程減輕許多可能出現在一個線程上運行的多個方法的同步問題上執行請求序列化的選擇器。不同於基於端口的源、 執行器源從中移除本身運行循環後它執行其選擇器。
注:在 OS X v10.5 之前執行選擇器源主要是用於將消息發送到主線程,但在 OS X v10.5 和以後和在 iOS 中,你可以使用它們來將消息發送到任何線程。
當在另一個線程上執行一個選擇器、 目標線程必須有一個積極的運行的循環。對於您創建的線程,這意味著等待直到您的代碼顯式啟動運行的循環。因為主線程啟動其自身運行的循環,然而你可以開始發布在該線程上的調用應用程序調用applicationDidFinishLaunching:應用程序委托的方法。所有已排隊的運行的循環過程執行選擇器調用每次通過循環、 而不是在每個循環迭代期間處理一個。
文本 3 2列出了NSObject可用於在其他線程上執行選擇器上定義的方法。因為這些方法在NSObject上的聲明、 您可以使用它們從任何線程在那裡你有目標 C 交互、 包括 POSIX 線程的訪問。這些方法實際上不會創建一個新線程來執行選擇器。
方法
可選項
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
在該線程的下一個運行的循環周期期間在應用程序的主線程上執行指定的選擇器。這些方法為您提供了阻止當前線程、 直到執行了選擇器選項。
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
在您具有一個NSThread對象的任何線程上執行指定的選擇器。這些方法為您提供了阻止當前線程、 直到執行了選擇器選項。
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
在當前線程上執行指定的選擇器下、 一個運行的循環周期期間和之後的可選的延遲期。因為它一直等到下一個的運行的循環周期,來執行選擇器這些方法提供了自動的迷你延遲從當前正在執行的代碼。多個隊列中選擇器是他們被排隊的順序執行一個接一個。
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:
使您可以取消發送到當前線程使用的消息performSelector:withObject:afterDelay:或performSelector:withObject:afterDelay:inModes:方法。
有關每種方法的詳細信息請參閱NSObject 類引用.
計時器來源交付事件同步在預設時間在未來對您的線程。計時器是一個線程來通知本身的方式做一些事情。例如,一個搜索欄,可以使用計時器來啟動自動搜索,一旦連續關鍵筆畫從用戶之間通過一定的時間。該延遲時間的使用使用戶有機會盡可能在開始搜索之前鍵入盡可能多的所需的搜索字符串。
雖然它可以生成基於時間的通知,一個計時器不是一個實時的機制。像輸入源,計時器是您運行循環的具體模式與相關聯的。計時器不是當前正在監視的運行循環的模式,它不會觸發,直到您運行的循環運行計時器的支持模式之一。同樣地,如果一個計時器觸發時運行的循環正在執行的處理程序例程,計時器就會等到下一次運行循環中要調用的處理程序例程。如果運行的循環不在所有運行的永遠不會引發計時器。
您可以配置計時器,以便生成事件,僅一次或多次。重復的計時器重新安排本身自動基於預定的發射時間,不是實際射擊。例如,如果計時器預定在某一特定時間和每隔 5 秒鐘後,消防,預定的發射時間將總是落在原來的 5 秒的時間間隔,即使實際射擊時間獲取延遲。如果點火時間延遲這麼多它會錯過一個或多個計劃的觸發次數,計時器是僅觸發一次錯過了時間。射擊後錯過了期限,計時器是重新安排在下一個預定的發射時間。
有關配置計時器來源的詳細信息,請參閱配置計時器來源。參考信息,請參閱NSTimer 類引用或CFRunLoopTimer 引用.
與來源,火適當的異步或同步事件發生時運行觀察員特別地點消防運行循環本身的執行過程中的循環。您可以使用運行的循環觀察員准備您的線程來處理某一特定的事件或准備該線程之前它進入睡眠狀態。在您運行的循環中,可以將運行的循環觀察員關聯以下事件:
到運行循環入口。
當運行的循環是對進程的計時器。
當運行的循環是對進程的輸入源。
當運行的循環馬上就要去睡覺。
當運行的循環已經吵醒了、 但是之前它已經處理的事件的醒了。
從運行循環退出。
您可以添加應用程序在使用核心基礎運行的循環觀察員。若要創建一個運行的循環的觀察者、 你創建CFRunLoopObserverRef不透明類型的新的實例。您的自定義回調函數和它感興趣的活動進行跟蹤這種類型。
一次或多次,可以使用類似於計時器,運行循環觀察員。之後它將觸發,而重復的觀察員仍然附著,一次性的觀察員就從運行循環中移除本身。您指定的觀察員是否運行一次或多次,當您創建它時。
有關如何創建運行循環觀察員的示例,請參閱配置運行循環。參考信息,請參閱CFRunLoopObserver 參考.
每次運行它時,您的線程運行的循環處理掛起的事件,並生成通知的任何附加的觀察員。這一點單是非常特定的並且是,如下所示:
通知觀測者已經進入了運行的循環。
通知觀測者任何准備就緒的計時器、 就要開火。
通知觀測者並非基於端口的任何輸入的源將要火。
火是准備射擊任何非基於端口的輸入的源。
如果基於端口的輸入的源是准備好了,等待到火災立即處理該事件。請轉到步驟 9。
通知觀察器線程即將入睡。
使線程進入沉睡,直到發生下列事件之一:
事件到達的基於端口的輸入源。
計時器將激發。
為運行回路設置的超時值將到期。
運行的循環顯式地吵醒了。
通知觀察器線程就醒了。
處理掛起的事件。
如果一個用戶定義的計時器被解雇,處理計時器事件並重新啟動循環。請轉到步驟 2。
如果一個輸入的源發射、 將事件傳送。
如果運行的循環被顯式地吵醒了,但還不超時請重新啟動循環。請轉到步驟 2。
通知觀察器運行的循環已退出。
因為定時器和輸入的源的觀察員通知被交付之前其實發生這些事件時、 可能會的通知時間與實際事件的時間之間的差距。如果這些事件之間的間隔時間是至關重要的可以使用的睡眠和清醒的睡眠通知以幫助您關聯的實際事件之間的間隔時間。
因為定時器和其他周期性的事件被交付運行的循環運行時、 繞過該循環擾亂了這些事件的交貨。只要您執行鼠標跟蹤例程通過進入循環並一再請求從應用程序中的事件、 就會發生這種行為的典型例子。因為您的代碼直接抓的事件,而不是讓應用程序發送這些事件通常情況下主動計時器將無法消防直到後你的鼠標跟蹤程序退出並返回到應用程序的控制。
一個運行的循環可以被顯式地喚醒使用運行的循環對象。其他事件也可能導致循環的運行、 吵醒了。這樣可以立即、 處理的輸入的源、 而不是等到一些其他事件發生時、 後點查詢即可、 添加另一個非基於端口的輸入的源醒來運行循環。
您需要顯式運行一個運行的循環的唯一時間是當您為您的應用程序創建第二個線程。為您的應用程序的主線程運行的循環是一塊關鍵的基礎設施。其結果是,應用程序框架提供運行主應用程序循環的代碼並且自動啟動該循環。UIApplication在 iOS 中的run方法NSApplication在 OS X) 中啟動應用程序的主循環作為正常啟動序列的一部分。如果您使用 Xcode 模板項目來創建您的應用程序、 您應該不需要顯式調用這些例程。
對於第二個線程,您需要決定是否運行的循環是必要的和如果是配置並啟動它自己。你不需要在所有情況下啟動一個線程運行的循環。後點查詢即可,如果您使用一個線程來執行一些長時間運行和預定的任務您可能可以避免開始運行的循環。運行的循環供在哪裡你想要更多的交互性與線程的情況。後點查詢即可,您需要啟動一個運行的循環,如果您計劃執行以下任一操作:
使用端口或自定義的輸入的源、 與其他線程進行通信。
使用計時器的線程上。
使用任何performSelector......可可應用中的方法。
保持大約要執行定期任務的線程。
如果您選擇使用一個運行的循環、 配置和安裝程序是簡單的。作為與所有線程編程雖然、 你應該退出你第二個線程在適當情況下的預案。它總是不如讓它退出、 比來迫使它終止干淨利落地結束一個線程。有關如何配置並退出運行的循環信息描述在使用運行循環對象.
然後運行它。 運行的循環對象提供主界面添加輸入的源、 計時器和運行循環觀察員到您運行的循環每個線程具有與它關聯的單個運行的循環對象。可可、 此對象是NSRunLoop類的一個實例。在一個低級別的應用程序、 它是指向CFRunLoopRef不透明類型的指針。
若要獲取當前線程運行的循環,您使用下列操作之一:
可可粉的應用程序中,使用NSRunLoop的currentRunLoop類方法來檢索一個NSRunLoop對象。
使用CFRunLoopGetCurrent函數。
雖然它們不是免費的橋接的類型,你可以從NSRunLoop對象時所需的CFRunLoopRef不透明類型。NSRunLoop類定義一個getCFRunLoop方法,返回一個CFRunLoopRef類型,您可以將傳遞給核心基礎例程。因為這兩個對象引用相同的運行循環,則可以根據需要混合使用對NSRunLoop對象和CFRunLoopRef不透明類型的調用。
在輔助線程上運行一個運行的循環之前,您必須向其添加至少一個輸入的源或計時器。如果運行的循環並沒有任何來源監測,它退出立即當您嘗試運行它。如何將源添加到一個運行循環示例,請參見配置運行循環來源.
除了安裝源,您也可以安裝運行的循環觀察員,並用於檢測不同的執行階段的運行循環。若要安裝一個運行的循環的觀察者,你創建一個CFRunLoopObserverRef的不透明類型,並使用CFRunLoopAddObserver函數來將其添加到您運行的循環。運行的循環使用核心基礎,即使對於可可應用程序必須創建的觀察員。
清單 3-1顯示某個線程的運行的循環觀察員重視其運行的循環的主的例程。該示例的目的是向您展示如何創建一個運行的循環的觀察者,所以代碼簡單地設置運行的循環觀察員來監測所有運行循環活動。(未顯示) 的基本的處理程序例程只是記錄運行的循環活動,因為它處理計時器請求。
清單 3-1創建一個運行的循環的觀察者
- (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);
}
配置長壽線程運行的循環時,最好添加至少一個輸入的源接收消息。雖然只用了附加,一旦計時器觸發一個定時器可以輸入運行的循環,它是通常將失效,這然後將導致循環的運行,退出。附加一個重復的計時器可以保持運行的循環運行較長的時間,但會涉及定期來喚醒你的生命線,是有效地另一種形式的輪詢計時器的觸發頻率。與此相反的是,一個輸入的源等待事件發生,保持您的線程睡著了,直到它。
開始運行的循環是只需在您的應用程序的輔助線程。一個運行的循環必須有至少一個輸入的源或要監視計時器。如果一個未連接,運行的循環會立即退出。
有幾種方法來啟動運行的循環,包括以下內容:
無條件地
與設置的時間限制
在一個特定的模式
無條件地進入你運行的循環是最簡單的選項,但它也是最不可取。無條件地運行您運行的循環的線程放入一個永久的循環,使您運行循環本身很少控制。您可以添加和刪除輸入的源和計時器,但停止運行的循環的必由之路是殺了它。也是沒有辦法在自定義模式下運行運行的循環。
而不是無條件地運行一個運行的循環、 它是更好地運行的循環運行有一個超時值。當您使用一個超時值時,運行的循環運行直到事件到達或所分配的時間過期。如果事件發生時,該事件派往處理的處理程序然後運行的循環退出。您的代碼、 然後可以重新啟動運行的循環來處理下一個事件。如果所分配的時間過期相反、 你可以簡單地重新啟動運行的循環或利用這段時間做任何必要的家務。
除了一個超時值,還可以運行您運行的循環使用一種特定的模式。模式和超時值並不相互排斥、 都可以用當啟動一個運行的循環。模式限制的來源、 向運行循環發送事件和在環路模式下運行的更多詳細介紹的類型.
清單 3-2顯示線程主入口例程的主干的版本。在此示例中的關鍵部分顯示一個運行循環的基本結構。從本質上說,你添加您的輸入的源和定時器運行循環然後反復調用例程來啟動運行的循環之一。每次運行的循環例程返回、 你看看如果我們任何條件會出現可能需要退出線程。該示例使用運行循環例程、 以便它可以檢查返回的結果並且確定為什麼運行的循環退出的核心基礎。讓運行的循環運行以類似的方式如果您使用的可可並不需要檢查返回值。 您也可以使用NSRunLoop類的方法(運行循環調用NSRunLoop類的方法的示例請參閱清單 3 14 .)
清單 3 2運行運行的循環
- (void)skeletonThreadMain
{
// Set up an autorelease pool here if not using garbage collection.
BOOL done = NO;
// Add your sources or timers to the run loop and do any other setup.
do
{
// Start the run loop but return after each source is handled.
SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
// If a source explicitly stopped the run loop, or if there are no
// sources or timers, go ahead 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.
}
很可能要運行運行的循環遞歸。換句話說,CFRunLoopRun、 CFRunLoopRunInMode、 從內部處理程序例程的輸入的源或計時器。 你可以調用或NSRunLoop方法中的任何啟動運行的循環當這樣做的過程中、 您可以使用任何你想要運行嵌套的循環、 包括正在使用的模式、 由外部運行循環運行的模式。
有兩種方法可以使一個運行的循環退出之前它已經處理的事件:
配置循環的運行、 運行有一個超時值。
告訴運行的循環停止。
使用超時值是當然首選、 如果你能做到。指定一個超時值,讓運行的循環完成所有其正常的處理包括傳遞通知運行循環觀察員之前退出運行。
停止使用CFRunLoopStop函數的顯式運行的循環產生的結果類似於超時。運行的循環發出任何剩余的運行循環通知、 然後退出。區別是您可以使用這種技術、 你開始無條件的運行循環。
雖然刪除運行的循環輸入的源和定時器也可能會導致循環的運行,退出這不是停止運行的循環的可靠方式。一些系統例程將輸入的源添加到一個運行的循環來處理所需的事件。因為您的代碼可能不知道這些輸入的來源,它將無法刪除它們這會阻止運行的循環退出。
線程安全而異、 具體取決於哪個 API 使用來操縱你運行的循環。核心基礎中的函數都是線程安全的一般、 可以從任何線程調用。然而,它是循環的仍然良好的做法,這樣做從擁有盡可能運行的循環的線程。 如果您執行的操作來修改該配置運行
可可NSRunLoop類本質上不是線程安全作為其核心基礎相對物。如果你在使用NSRunLoop類修改您運行的循環、 你應該做所以只能從同一個線程擁有運行循環。添加輸入的源或計時器到屬於一個不同的線程運行循環可能導致您的代碼與崩潰或以意想不到的方式行事。
以下部分說明如何設置不同類型的輸入源可可和核心基礎中的示例。
創建一個自定義的輸入的源包括定義以下內容:
要你來處理的輸入的源的信息。
調度程序例程、 讓有興趣的客戶知道如何聯絡你的輸入的源。
要執行任何客戶端發送的請求的處理程序例程。
取消例程、 以使你的輸入的源無效。
由於您創建自定義的輸入的源,來處理自定義信息實際配置旨在采取靈活的態度。調度程序,處理程序和取消例程是關鍵的例程你幾乎總是需要您自定義的輸入源。大多數其余的輸入的源的行為,然而發生在那些處理程序例程之外。它是由您自己定義的機制傳遞到你的輸入源的數據和交流你給其他線程的輸入源的存在。 後點查詢即可
圖 3-2顯示了一個示例配置自定義的輸入源。在此示例中、 應用程序的主線程維護輸入源、 自定義命令緩沖區的輸入源、 並在其安裝的輸入的源運行的循環的引用。當主線程的任務,它要上交掉到輔助線程時它會發送到命令緩沖區以及任何所需要的輔助線程開始執行該任務的信息的命令。(因為主線程和輔助線程的輸入的源到命令緩沖區的訪問訪問必須同步。)一旦發布了命令、 主線程發出信號的輸入的源和喚醒工作線程運行循環。接到命令醒來,運行的循環調用處理程序的輸入源處理命令在命令緩沖區中發現。
圖 3-2運行自定義的輸入的源
以下各節從前面的圖解釋執行自定義的輸入源,並顯示關鍵代碼您將需要實現。
定義自定義的輸入的源要求核心基礎例程、 可以配置您運行的循環源並將其附加到運行的循環的使用。雖然基本的處理程序是基於 c 語言的函數但這並不排除你從寫作為這些職能的包裝和使用目標 C 或 c + + 來實現您的代碼的身體。
圖 3-2中介紹的輸入的源使用目標 C 對象來管理命令緩沖區和配合運行循環。清單 3-3顯示此對象的定義。並使用該緩沖區接收來自其他線程的消息。 RunLoopSource對象管理命令緩沖區此列表還顯示的RunLoopContext交互、 是真的只是一個用來傳遞一個RunLoopSource對象和應用程序的主線程運行的循環引用的容器對象的定義。
清單 3-3自定義輸入的源對象定義
@interface RunLoopSource : NSObject
{
CFRunLoopSourceRef runLoopSource;
NSMutableArray* commands;
}
- (id)init;
- (void)addToCurrentRunLoop;
- (void)invalidate;
// Handler method
- (void)sourceFired;
// Client interface for registering commands to process
- (void)addCommand:(NSInteger)command withData:(id)data;
- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runloop;
@end
// These are the CFRunLoopSourceRef callback functions.
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
void RunLoopSourcePerformRoutine (void *info);
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
// RunLoopContext is a container object used during registration of the input source.
@interface RunLoopContext : NSObject
{
CFRunLoopRef runLoop;
RunLoopSource* source;
}
@property (readonly) CFRunLoopRef runLoop;
@property (readonly) RunLoopSource* source;
- (id)initWithSource:(RunLoopSource*)src andLoop:(CFRunLoopRef)loop;
@end
雖然目標 C 代碼管理的輸入源的自定義數據、 附加的輸入的源到一個運行的循環需要基於 C 的回調函數。你實際上將運行的循環源附加到您運行的循環並且清單 3-4中所示時、 將調用這些函數的第一個。因為此輸入的源具有只有一個客戶端 (主線程)、 它使用調度功能發送一條消息、 自身注冊應用程序委托在該線程上。當委托想要溝通的輸入源時、 它使用的信息在RunLoopContext對象中這樣做。
清單 3-4調度運行的循環源
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
RunLoopSource* obj = (RunLoopSource*)info;
AppDelegate* del = [AppDelegate sharedAppDelegate];
RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
[del performSelectorOnMainThread:@selector(registerSource:)
withObject:theContext waitUntilDone:NO];
}
其中一個最重要的回調例程是用於處理自定義數據、 當您輸入的源信號。清單 3-5顯示執行回調例程與RunLoopSource對象相關聯。此函數只是轉發請求做這項工作給sourceFired方法然後處理當前的命令緩沖區中的任何命令。
清單 3-5在輸入源中執行工作
void RunLoopSourcePerformRoutine (void *info)
{
RunLoopSource* obj = (RunLoopSource*)info;
[obj sourceFired];
}
如果你曾經從其運行的循環使用CFRunLoopSourceInvalidate函數中刪除你的輸入的源、 系統調用你的輸入的源取消例程。您可以使用這個例程來通知客戶端,你的輸入的源已不再有效他們應該刪除任何對它的引用。清單 3-6顯示了RunLoopSource對象注冊取消回調例程。此函數將另一個RunLoopContext對象發送到應用程序委托、 但是這一次問要移除對運行的循環源的引用的委托。
清單 3-6無效的輸入的源
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
RunLoopSource* obj = (RunLoopSource*)info;
AppDelegate* del = [AppDelegate sharedAppDelegate];
RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
[del performSelectorOnMainThread:@selector(removeSource:)
withObject:theContext waitUntilDone:YES];
}
注:應用程序委托的代碼registerSource:和removeSource:方法所示協調與客戶端的輸入源.
清單 3-7顯示了RunLoopSource類的init和addToCurrentRunLoop方法。init方法創建的CFRunLoopSourceRef不透明類型實際上必須附加到運行的循環。這樣的回調例程有指向對象的指針、 它將作為上下文信息傳遞RunLoopSource對象本身。此時調用RunLoopSourceScheduleRoutine回調函數就不會發生輸入源的安裝。 直到輔助線程調用addToCurrentRunLoop方法一旦輸入的源添加到運行循環時,該線程可以運行其運行的循環以在其上等待。
清單 3-7安裝運行的循環源
- (id)init
{
CFRunLoopSourceContext context = {0, self, NULL, NULL, NULL, NULL, NULL,
&RunLoopSourceScheduleRoutine,
RunLoopSourceCancelRoutine,
RunLoopSourcePerformRoutine};
runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);
commands = [[NSMutableArray alloc] init];
return self;
}
- (void)addToCurrentRunLoop
{
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
}
對你有用的輸入源、 您需要對它進行操作和信號從另一個線程。輸入源的整點是要其關聯的線程休眠直到有事做。這一事實,就必須有您的應用程序中的其他線程的輸入源的了解並有某種與它交流。
一種方法通知客戶關於你的輸入源是發送注冊請求時你的輸入的源第一次安裝在其運行的環路上。當多個客戶端你想要或者您可以簡單地注冊它與一些然後供應你感興趣的客戶端的輸入的源的中央機構、 您可以注冊與您輸入的源。清單 3 8顯示由應用程序委托定義和調用時調用RunLoopSource對象調度函數的注冊方法。此方法接收RunLoopSource對象所提供的RunLoopContext交互並將其添加到自己的源列表。此列表還顯示用於注銷的輸入的源、 當它從它運行的循環中移除該例程。
清單 3 8注冊和與應用程序委托刪除輸入的源
- (void)registerSource:(RunLoopContext*)sourceInfo;
{
[sourcesToPing addObject:sourceInfo];
}
- (void)removeSource:(RunLoopContext*)sourceInfo
{
id objToRemove = nil;
for (RunLoopContext* context in sourcesToPing)
{
if ([context isEqual:sourceInfo])
{
objToRemove = context;
break;
}
}
if (objToRemove)
[sourcesToPing removeObject:objToRemove];
}
注:清單 3 4和清單 3 6所示的調用的方法、 在前面的清單中的回調函數.
它不插手干預其數據的輸入源後,客戶端必須信號源其運行的循環中醒來。信號源讓運行的循環知道的來源是准備進行處理。因為信號發生時,該線程可能是睡著了,醒來你應該總是會運行循環顯式。如果不這樣做可能會導致延遲處理的輸入的源。
清單 3 9顯示了RunLoopSource對象的fireCommandsOnRunLoop方法。當他們准備源來處理他們添加到緩沖區的命令時、 客戶端調用此方法。
清單 3-9醒來的運行的循環
- (void)fireCommandsOnRunLoop:(CFRunLoopRef)runloop
{
CFRunLoopSourceSignal(runLoopSource);
CFRunLoopWakeUp(runloop);
}
注:你應該永遠不會嘗試處理SIGHUP或其他類型的進程級信號通過消息傳遞自定義的輸入的源。醒來運行循環功能是核心基礎不信號安全、 不應在您的應用程序信號處理程序例程內部使用。信號處理程序例程的更多信息、 請參見sigaction手冊頁。
若要創建一個計時器來源、 所有你必須做是創建計時器對象並安排它在您運行的循環。可可、 您使用NSTimer類來創建新的計時器對象而在核心基礎你使用CFRunLoopTimerRef不透明類型。在內部NSTimer類是簡單地擴展提供了一些方便的功能如創建和安排一個計時器使用相同的方法的能力的核心基礎。
可可,您可以創建和安排一個計時器,一次全部使用這些類的方法之一:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
scheduledTimerWithTimeInterval:invocation:repeats:
這些方法創建計時器、 並將其添加到當前線程運行循環中的默認模式 (NSDefaultRunLoopMode)。您還可以安排一個計時器手動,NSTimer交互,然後將它添加到運行的循環使用如果你想通過創建您的addTimer:forMode: NSRunLoop的方法。這兩種技術基本上做同樣的事情、 但是給你不同級別的控制計時器的配置。後點查詢即可、 如果您創建的計時器、 並手動將它添加到運行的循環、 你可以使用非默認模式的模式。清單 3-10顯示了如何創建計時器使用這兩種技術。第一個計時器有 1 秒但然後火災初始延遲定期每 0.1 秒後顯示。第二個計時器開始射擊後最初的 0.2 秒的延遲、 然後觸發後、 每 0.2 秒。
清單 3-10創建和計劃使用 NSTimer 計時器
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];
清單 3 11顯示配置計時器使用核心基礎功能所需的代碼。盡管本示例沒有通過上下文結構中的任何用戶定義的信息,您可以使用這種結構傳遞任何自定義的數據你需要為你的計時器。這種結構的內容的更多信息、 請參見CFRunLoopTimer 參考中的說明.
清單 3 11創建和計劃使用核心基礎計時器
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);
可可和核心基礎之間的線程或進程之間傳達提供基於端口的對象。以下各節展示了如何使用幾種不同類型的端口的端口通信設置。
要建立與NSMachPort對象的本地連接、 您創建端口對象並將其添加到您的主線程運行循環。當啟動輔助線程、 你將同一個對象傳遞給您的線程的入口點函數。輔助線程可以使用相同的對象、 將消息發送回您的主線程。
清單 3-12顯示啟動輔助工作線程的主線程代碼。由於可可框架執行很多干預的步驟配置端口和運行的循環launchThread方法是明顯短於其核心基礎等效 (清單 3-17) ;然而這兩個行為是幾乎完全相同的。一個區別是而不是發送到輔助線程的本地端口的名稱、 此方法將發送NSPort對象直接。
清單 3-12主線程啟動方法
- (void)launchThread
{
NSPort* myPort = [NSMachPort port];
if (myPort)
{
// This class handles incoming port messages.
[myPort setDelegate:self];
// Install the port as an input source on the current run loop.
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
// Detach the thread. Let the worker release the port.
[NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:)
toTarget:[MyWorkerClass class] withObject:myPort];
}
}
要設置您的線程之間的雙向通信信道、 你可能想要向你主線程檢查在消息中發送其自己的本地端口的工作線程。接受檢查的消息讓你知道一切都很好在發射第二個線程的主線程、 也給你的方法來將進一步消息發送到該線程。
清單 3 13顯示handlePortMessage:方法的主線程。當數據到達線程的本地端口時、 將調用此方法。當檢查在消息到達時,該方法直接從端口消息檢索輔助線程的端口並將其保存以備後用。
清單 3 13處理馬赫端口消息
#define kCheckinMessage 100
// Handle responses from the worker thread.
- (void)handlePortMessage:(NSPortMessage *)portMessage
{
unsigned int message = [portMessage msgid];
NSPort* distantPort = nil;
if (message == kCheckinMessage)
{
// Get the worker thread’s communications port.
distantPort = [portMessage sendPort];
// Retain and save the worker port for later use.
[self storeDistantPort:distantPort];
}
else
{
// Handle other messages.
}
}
對於輔助工作線程,你必須配置線程並使用指定的端口進行通信信息反饋給主線程。
清單 3 14顯示了用於設置工作線程的代碼。後創建的線程 autorelease 池、 該方法將創建工人對象開車線程執行。工人交互sendCheckinMessage:方法 (清單 3 15並將檢查在消息發送回主線程。 所示) 創建工作線程的本地端口
清單 3 14啟動輔助線程使用馬赫端口
+(void)LaunchThreadWithPort:(id)inData
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Set up the connection between this thread and the main thread.
NSPort* distantPort = (NSPort*)inData;
MyWorkerClass* workerObj = [[self alloc] init];
[workerObj sendCheckinMessage:distantPort];
[distantPort release];
// Let the run loop process things.
do
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
while (![workerObj shouldExit]);
[workerObj release];
[pool release];
}
在使用NSMachPort時本地和遠程線程可以使用相同的端口對象的線程之間的單向通信。換句話說由一個線程創建的本地端口對象成為另一個線程的遠程端口對象。
清單 3 15顯示簽入常規的輔助線程。此方法設置其自身未來通信的本地端口、 然後將檢查在消息發送回主線程。該方法使用端口對象在收到此方法設置其自身未來通信的本地端口、 然後將檢查在消息發送回主線程。方法作為消息的目標。
清單 3 15發送使用馬赫端口的檢查消息
// Worker thread check-in method
- (void)sendCheckinMessage:(NSPort*)outPort
{
// Retain and save the remote port for future use.
[self setRemotePort:outPort];
// Create and configure the worker thread port.
NSPort* myPort = [NSMachPort port];
[myPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
// Create the check-in message.
NSPortMessage* messageObj = [[NSPortMessage alloc] initWithSendPort:outPort
receivePort:myPort components:nil];
if (messageObj)
{
// Finish configuring the message and send it immediately.
[messageObj setMsgId:setMsgid:kCheckinMessage];
[messageObj sendBeforeDate:[NSDate date]];
}
}
要建立與你不能簡單地通過端口對象線程之間。要建立與NSMessagePort對象的本地連接對象的本地連接、 你不能簡單地通過端口對象線程之間。遠程消息端口必須按名稱獲取。制作可可中的這可能需要具有特定名稱注冊你的本地端口、 然後順帶一提、 名稱到遠程線程、 以便它可以獲取適當的端口對象進行通信。遠程消息端口必須按名稱獲取。顯示端口創建和注冊過程中要使用消息端口的情況。
清單 3 16注冊消息端口
NSPort* localPort = [[NSMessagePort alloc] init];
// Configure the object and add it to the current run loop.
[localPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:localPort forMode:NSDefaultRunLoopMode];
// Register the port using a specific name. The name must be unique.
NSString* localPortName = [NSString stringWithFormat:@MyPortName];
[[NSMessagePortNameServer sharedInstance] registerPort:localPort
name:localPortName];
本節演示如何設置您的應用程序的主線程和輔助線程使用核心基礎之間的雙向通信通道.
清單 3 17顯示了應用程序的主線程啟動輔助線程所調用的代碼。代碼做的第一件事設置CFMessagePortRef 不透明類型以偵聽消息從工作線程。輔助線程需要要使該連接的端口的名稱、 以便字符串值傳遞給工作線程的入口點函數。端口名稱一般應在當前用戶上下文內唯一;否則您可能會遇到沖突。
清單 3 17附加到一個新的線程的核心基礎消息端口
#define kThreadStackSize (8 *4096)
OSStatus MySpawnThread()
{
// Create a local port for receiving responses.
CFStringRef myPortName;
CFMessagePortRef myPort;
CFRunLoopSourceRef rlSource;
CFMessagePortContext context = {0, NULL, NULL, NULL, NULL};
Boolean shouldFreeInfo;
// Create a string with the port name.
myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR(com.myapp.MainThread));
// Create the port.
myPort = CFMessagePortCreateLocal(NULL,
myPortName,
&MainThreadResponseHandler,
&context,
&shouldFreeInfo);
if (myPort != NULL)
{
// The port was successfully created.
// Now create a run loop source for it.
rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
if (rlSource)
{
// Add the source to the current run loop.
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
// Once installed, these can be freed.
CFRelease(myPort);
CFRelease(rlSource);
}
}
// Create the thread and continue processing.
MPTaskID taskID;
return(MPCreateTask(&ServerThreadEntryPoint,
(void*)myPortName,
kThreadStackSize,
NULL,
NULL,
NULL,
0,
&taskID));
}
隨著港口安裝和啟動線程、 主線程可以繼續其定期執行等待線程來簽到時。當檢查在消息到達時、 它被調度到主線程MainThreadResponseHandler區域、清單 3 18中所示。此函數提取工作線程的端口名稱、 並創建未來的通信渠道。
清單 3-18收到的簽入的消息
#define kCheckinMessage 100
// Main thread port message handler
CFDataRef MainThreadResponseHandler(CFMessagePortRef local,
SInt32 msgid,
CFDataRef data,
void* info)
{
if (msgid == kCheckinMessage)
{
CFMessagePortRef messagePort;
CFStringRef threadPortName;
CFIndex bufferLength = CFDataGetLength(data);
UInt8* buffer = CFAllocatorAllocate(NULL, bufferLength, 0);
CFDataGetBytes(data, CFRangeMake(0, bufferLength), buffer);
threadPortName = CFStringCreateWithBytes (NULL, buffer, bufferLength, kCFStringEncodingASCII, FALSE);
// You must obtain a remote message port by name.
messagePort = CFMessagePortCreateRemote(NULL, (CFStringRef)threadPortName);
if (messagePort)
{
// Retain and save the thread’s comm port for future reference.
AddPortToListOfActiveThreads(messagePort);
// Since the port is retained by the previous function, release
// it here.
CFRelease(messagePort);
}
// Clean up.
CFRelease(threadPortName);
CFAllocatorDeallocate(NULL, buffer);
}
else
{
// Process other messages.
}
return NULL;
}
與主線程配置、 剩下的唯一的事是為新創建的輔助線程以創建它自己的端口、 並檢查清單 3 19顯示了工作線程的入口點函數的英寸。函數提取主線程端口名稱、 並使用它來創建一個遠程連接回主線程。並檢查在消息發送到主線程包含本地端口的名稱。該函數然後為自己創建一個本地端口、 安裝端口的線程運行循環中
清單 3 19設置線程結構
OSStatus ServerThreadEntryPoint(void* param)
{
// Create the remote port to the main thread.
CFMessagePortRef mainThreadPort;
CFStringRef portName = (CFStringRef)param;
mainThreadPort = CFMessagePortCreateRemote(NULL, portName);
// Free the string that was passed in param.
CFRelease(portName);
// Create a port for the worker thread.
CFStringRef myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR(com.MyApp.Thread-%d), MPCurrentTaskID());
// Store the port in this thread’s context info for later reference.
CFMessagePortContext context = {0, mainThreadPort, NULL, NULL, NULL};
Boolean shouldFreeInfo;
Boolean shouldAbort = TRUE;
CFMessagePortRef myPort = CFMessagePortCreateLocal(NULL,
myPortName,
&ProcessClientRequest,
&context,
&shouldFreeInfo);
if (shouldFreeInfo)
{
// Couldn't create a local port, so kill the thread.
MPExit(0);
}
CFRunLoopSourceRef rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
if (!rlSource)
{
// Couldn't create a local port, so kill the thread.
MPExit(0);
}
// Add the source to the current run loop.
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
// Once installed, these can be freed.
CFRelease(myPort);
CFRelease(rlSource);
// Package up the port name and send the check-in message.
CFDataRef returnData = nil;
CFDataRef outData;
CFIndex stringLength = CFStringGetLength(myPortName);
UInt8* buffer = CFAllocatorAllocate(NULL, stringLength, 0);
CFStringGetBytes(myPortName,
CFRangeMake(0,stringLength),
kCFStringEncodingASCII,
0,
FALSE,
buffer,
stringLength,
NULL);
outData = CFDataCreate(NULL, buffer, stringLength);
CFMessagePortSendRequest(mainThreadPort, kCheckinMessage, outData, 0.1, 0.0, NULL, NULL);
// Clean up thread data structures.
CFRelease(outData);
CFAllocatorDeallocate(NULL, buffer);
// Enter the run loop.
CFRunLoopRun();
}
一旦進入其運行的循環由ProcessClientRequest函數處理發送到線程的端口的所有未來事件。該函數的實現取決於類型的工作線程並不會顯示在這裡。
下一個上一頁