RunLoop(官方文檔翻譯)

循環運行

運行循環是與線程相關聯的基本基礎設施的一部分。一個運行循環是用於調度工做,並協調接收傳入事件的事件處理循環。一個運行循環的目的是讓你的線程繁忙時,有工做要作,把你的線程時有沒有睡覺。html

循環運行管理不徹底是自動的。你還必須設計線程的代碼開始在適當的時候運行循環和響應傳入的事件。不管可可和核心基礎提供了運行循環對象,以幫助您配置和管理線程的運行循環。您的應用程序並不須要顯式地建立這些對象; 每一個線程,包括應用程序的主線程,都有一個相關的運行循環對象。只有輔助線程但須要明確地運行他們的運行循環。該應用程序框架自動設置和運行在主線程中運行循環的應用程序啓動過程的一部分。編程

如下部分提供了有關循環運行以及如何將它們配置爲您的應用程序的更多信息。有關運行循環對象的其餘信息,請參閱NSRunLoop類參考CFRunLoop參考安全

一個運行循環的解剖

一個運行循環是很是喜歡它的名字聽起來。是一個循環的線程進入並用來響應於輸入事件執行的事件處理程序。您的代碼提供了用於實現循環,換句話說,你的代碼提供了運行的實際環部分的控制語句whilefor循環驅動的運行循環。在你的循環,你使用一個運行循環對象「運行」接收事件並調用處理程序安裝在事件處理代碼。數據結構

一個運行環接收來自兩個不一樣類型的源事件。輸入源傳遞異步事件,一般消息從另外一個線程或從不一樣的應用程序。定時源提供的同步事件,在預約的時間發生的或重複間隔。這兩種類型的源的使用應用特定處理例程,當它到達處理該事件。併發

圖3-1顯示了一個運行循環和各類來源的概念結構。輸入源傳遞異步事件到相應的處理程序,並致使runUntilDate:方法(稱爲線程相關的NSRunLoop對象)來退出。定時源提供的事件他們的處理程序例程,但不會致使運行循環退出。app

圖3-1   運行循環及其來源結構一個運行循環及其來源結構框架

除了處理輸入源,循環運行也產生對運行循環的行爲的通知。註冊運行循環觀察者能夠收到這些通知,並利用它們的線程上作額外的處理。您可使用Core Foundation的在你的線程安裝運行循環觀察員。異步

如下各節提供有關運行循環的組成部分,並在其運行的模式的詳細信息。他們還描述被事件的處理期間,在不一樣的時間產生的通知。socket

運行循環模式

一個運行的循環模式輸入源和定時器的集合進行監測和運行循環觀察者的集合的通知。每次運行您的運行循環時,能夠指定(或隱或顯),在其中運行一個特定的「模式」。在運行過程當中循環的該通,只有與該模式相關聯的源被監視並能讀出它們的事件。(一樣,只有與該模式相關觀察員通知的運行循環的進度。)與其餘模式相關的源持有任何新的事件,直到經過適當的模式循環隨後的通行證。函數

在您的代碼,按名稱識別模式。不管可可和Core Foundation的定義默認的模式和一些經常使用的模式,用在你的代碼中指定這些模式串一塊兒。你能夠簡單的經過指定的模式名稱自定義字符串定義自定義模式。雖然分配給自定義模式的名稱是任意的,這些模式的內容都沒有。你必定必定要添加一個或多個輸入源,定時器,或運行循環觀察員爲其建立有用的模式。

您可使用模式特定經過您運行的循環中過濾掉干擾源事件。大多數時候,你會想在運行系統中定義的「默認」模式,您的運行循環。模態面板,可是,可能會在「模式」模式下運行。在此模式下,惟一來源相關的模式面板將提供事件的線程。對於輔助線程,你可使用自定義模式,以防止低優先級的來源,從在時間臨界操做提供事件。

注意:  基於所述事件,而不是事件的類型的來源模式鑑別。例如,您不會使用模式匹配只鼠標按下事件或只有鍵盤事件。你可使用模式來聽一組不一樣的端口,暫停定時器或者改變當前監視的來源和運行循環觀察員。

 

表3-1列出了可可和核心基礎與當您使用模式的描述以及定義的標準模式。名稱列列出你實際使用的常量來指定在你的代碼模式。

表3-1   預約義的運行循環模式

模式

名稱

描述

默認

NSDefaultRunLoopMode (可可)

kCFRunLoopDefaultMode (核心基金會)

默認模式是用於大多數操做之一。在大多數狀況下,你應該使用此模式啓動運行循環和配置您的輸入源。

鏈接

NSConnectionReplyMode (可可)

可可以使用此模式結合NSConnection對象監控的答覆。你應該不多須要本身使用此模式。

語氣

NSModalPanelRunLoopMode(可可)

可可以使用此模式來肯定用於模態面板的事件。

事件追蹤

NSEventTrackingRunLoopMode(可可)

可可以使用此模式限制在鼠標拖動循環和其餘類型的用戶界面跟蹤環路的輸入事件。

共模

NSRunLoopCommonModes (可可)

kCFRunLoopCommonModes (核心基金會)

這是經常使用的模式的可配置羣組。相關聯的輸入源與該模式還它與每一個組中的模式的關聯。對於Cocoa應用程序,這一套包括默認,模態和事件跟蹤默認模式。核心基金僅包括默認模式開始。您能夠添加自定義模式,使用設定的CFRunLoopAddCommonMode功能。

輸入源

輸入源傳遞異步事件,你的線程。事件的源取決於輸入源,它通常是兩種類別之一的類型。基於端口的輸入源監控應用程序的馬赫端口。自定義輸入源監控事件自定義來源。至於你的運行循環而言,它不該該的問題輸入源是基於端口或自定義。該系統一般實現了兩種類型,您可使用爲的就是輸入源。兩個源之間的惟一差異是它們是如何發出信號。基於端口的源內核發出信號,以及自定義來源必須手動從另外一個線程來通知。

當你建立輸入源,你把它分配給您的運行循環的一個或多個模式。的方式影響該輸入源是在任何給定時刻進行監控。在大多數狀況下,運行在默認模式下運行環路,但你能夠指定得自定義模式。若是輸入源是否是在當前監視模式下,它產生的任何事件都保持,直到運行循環在正確的模式下運行。

下面的部分描述了一些輸入源。

基於端口的源

可可和核心基礎提供內置的建立使用端口相關的對象和函數基於端口的輸入源的支持。例如,在可可,你歷來沒有直接在各建立一個輸入源。您只需建立端口對象,並使用方法NSPort到該端口添加到運行循環。端口對象爲您處理所須要的輸入源的建立和配置。

在覈心基礎,你必須手動建立這兩個港口,它的運行循環源。在這兩種狀況下,您使用的端口類型不透明(相關的功能CFMachPortRefCFMessagePortRefCFSocketRef)建立合適的對象。

有關如何設置和配置定製的基於端口的源示例,請參閱配置基於端口的輸入源

自定義輸入源

要建立自定義輸入源,必須使用與相關聯的功能CFRunLoopSourceRef在覈心基礎不透明的類型。您能夠配置使用多個回調函數的自定義輸入源。核心基礎調用在不一樣的點這些功能配置源,處理任何輸入事件,而且當它被從運行循環除去推倒源。

除了定義在事件到達自定義源的行爲,你還必須定義事件傳遞機制。源的這部分在一個單獨的線程運行,並負責提供其數據輸入源和信令它時,數據已準備好進行處理。該事件傳遞機制是由你,但沒必要過於複雜。

有關如何建立自定義輸入源的例子,請參閱定義自定義輸入源。對於自定義輸入源的參考信息,也見CFRunLoopSource參考

可可進行選擇來源

除了基於端口的源,可可定義了一個自定義輸入源,容許你在任何線程執行的選擇。像基於端口的源,執行選擇請求被序列化的目標線程上,緩解了可能與被在一個線程中運行多個方法同步發生的問題。不像基於端口的源,進行選擇源從運行循環中刪除它自己執行其選擇了。

注:  在此以前OS X v10.5上,進行選擇的來源大多用於將消息發送到主線程,但在OS X v10.5及其更高版本以及iOS中,你可使用它們將消息發送到任何線程。

 

當另外一個線程進行選擇,目標線程必須有一個有效運行循環。對於建立線程,這意味着等到你的代碼明確開始運行循環。因爲主線程啓動它本身的運行循環,可是,您能夠當即開始在該線程調用發佈的應用程序調用 applicationDidFinishLaunching:的應用程序委託的方法。運行循環處理全部排隊經過循環進行選擇每次通話時間,而不是每次循環迭代的過程當中處理的。

表3-2列出了定義的方法NSObject,可用於在其它線程執行選擇器。由於這些方法都宣佈對NSObject,你能夠從那裏你可使用Objective-C的對象的線程,包括POSIX線程使用它們。這些方法實際上並無建立新的線程來執行選擇。

表3-2   其餘線程上執行選擇

方法

描述

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參考

事件的運行循環序列

每次運行它的時候,你線程的run循環處理未完成的事件,並生成任何附加的觀察者通知。在它這是很是具體的,是以下順序:

  1. 通知運行循環已進入觀察員。

  2. 通知任何現成計時器即將開火觀察員。

  3. 通知未基於端口的任何輸入源即將開火觀察員。

  4. 大火非基於端口的輸入源是準備開火。

  5. 若是基於端口的輸入源已準備就緒,等待開火,當即處理該事件。轉至步驟9。

  6. 通知觀察者該線程將要睡覺。

  7. 把線程睡眠,直到如下事件之一發生:

    • 事件到達一個基於端口的輸入源。

    • 計時器火災。

    • 對於運行循環設置超時值到期。

    • 運行循環明確喚醒。

  8. 通知觀察者該線程剛剛醒來。

  9. 處理掛起的事件。

    • 若是用戶定義的計時器所觸發,過程計時器事件並從新啓動循環。轉到步驟2。

    • 若是輸入源發射,提供的事件。

    • 若是運行循環被明確喚醒但尚未超時,請從新啓動循環。轉到步驟2。

  10. 通知運行退出循環觀察員。

由於定時器和輸入源觀察者通知這些事件實際發生以前被傳遞,可能存在的通知的時間和實際事件的時間之間的間隙。若是這些事件之間的時間是相當重要的,你可使用睡眠和清醒 - 從睡眠的通知,以幫助您關聯的實際事件之間的時間。

由於當你運行運行的循環定時器等週期性事件傳遞,繞過該循環破壞這些事件的交付。當你輸入一個循環反覆請求從應用程序的事件實現鼠標跟蹤程序發生這種行爲的典型例子。由於你的代碼直接搶奪事件,而不是讓應用程序調度這些事件一般,活動的定時器將沒法觸發,直到後您的鼠標跟蹤程序退出並返回到應用程序控件。

一個運行循環能夠明確地喚醒使用運行循環對象。其餘事件也可能致使運行循環喚醒。例如,添加另外一非基於端口輸入源喚醒運行循環,使得輸入源能夠被當即處理,而不是等待直到其餘的事件發生。

當你使用一個運行循環?

你須要跑一跑環的惟一的時候,你爲應用程序建立輔助線程明確的。爲您的應用程序的主線程運行循環是基礎設施關鍵部分。其結果是,該應用框架上運行的主應用程序循環提供代碼並自動啓動該循環。該run方法UIApplication在iOS的(或NSApplication在OS X)啓動一個應用程序的主迴路的正常啓動序列的一部分。若是您使用的Xcode項目模板建立應用程序,你永遠不該該顯式調用這些例程。

對於輔助線程,你須要決定一個運行循環是不是必要的,若是是,配置和自行啓動。你並不須要在全部狀況下啓動一個線程的運行循環。例如,若是你使用一個線程來執行一些長時間運行和預約的任務,你可能避免啓動運行循環。運行迴路供您但願與線程更多的交互狀況。例如,你須要的,若是你打算作任何下列開始運行循環:

  • 使用端口或自定義輸入源與其餘線程溝通。

  • 使用線程計時器。

  • 使用任何performSelector......方法Cocoa應用程序。

  • 保持線程各地按期進行的任務。

若是你選擇使用一個運行循環,配置和設置很是簡單。如同全部的線程編程雖然,你應該在適當的場合退出您的輔助線程的計劃。它始終是更好地經過讓它退出,而不是迫使它終止徹底結束線程。關於如何配置和退出運行循環的信息中說明使用運行循環對象

使用運行循環對象

一個運行循環對象提供的主界面添加輸入源,定時器和運行環觀察員的運行循環,而後運行它。每一個線程都有與之關聯的單一運行循環對象。在可可,此對象的實例NSRunLoop類。在低級別的應用程序,它是一個指向CFRunLoopRef不透明型。

得到一個運行循環對象

爲了得到當前線程的運行循環,使用下列之一:

雖然他們不是免費橋接的類型,你能夠獲得一個CFRunLoopRef從不透明的類型NSRunLoop須要時對象。本NSRunLoop類定義了一個getCFRunLoop返回的方法CFRunLoopRef類型,你能夠傳遞給Core Foundation的例程。由於這兩個對象指的是相同的運行循環,能夠混合使用,在兩個電話NSRunLoop對象和CFRunLoopRef根據須要不透明型。

配置運行循環

在運行的輔助線程運行循環,你必須添加至少一個輸入源或定時器給它。若是運行循環沒有任何來源進行監控,當你嘗試運行它當即退出。有關如何源添加到一個運行循環示例,請參閱配置運行循環源

除了安裝源,還能夠安裝運行循環觀察,並用它們來檢測運行循環的不一樣的執行階段。要安裝運行循環的觀察者,您建立一個CFRunLoopObserverRef不透明的類型和使用CFRunLoopAddObserver功能將它添加到您的運行循環。運行循環觀察者必須使用核心基礎,即便是Cocoa應用程序建立。

清單3-1顯示了附加一個run loop的觀察者到它的運行循環線程的主程序。這個例子的目的是向您展現如何建立一個運行循環的觀察者,因此該代碼只是設置一個運行循環的觀察者來監控全部運行的循環活動。基本處理程序(未示出)簡單地記錄,由於它處理定時器的請求的運行循環活性。

清單3-1   建立一個運行循環的觀察者

 - (無效)threadMain
{
    //應用程序使用垃圾收集,所以不須要自動釋放池。
    NSRunLoop * myRunLoop = [NSRunLoop currentRunLoop]
 
    //建立一個運行循環的觀察者並將其鏈接到運行循環。
    CFRunLoopObserverContext背景= {0,自我,NULL,NULL,NULL};
    CFRunLoopObserverRef觀察者= CFRunLoopObserverCreate(kCFAllocatorDefault,
            kCFRunLoopAllActivities,YES,0,和myRunLoopObserver,與背景);
 
    若是(觀察員)
    {
        CFRunLoopRef CFLOOP = [myRunLoop getCFRunLoop]
        CFRunLoopAddObserver(CFLOOP,觀察者,kCFRunLoopDefaultMode);
    }
 
    //建立並安排計時器。
    [的NSTimer scheduledTimerWithTimeInterval:0.1目標:自我
                選擇:@選擇(doFireTimer :)用戶信息:無重複:YES];
 
    NSInteger的loopCount = 10;
    {
        //運行運行循環10次,讓計時器火災。
        [myRunLoop runUntilDate:[的NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    }
    而(loopCount);
}

當配置運行循環的長壽命線程,最好是添加至少一個輸入源到接收消息。雖然你能夠只鏈接定時器輸入運行循環,一旦定時器觸發,它一般是無效的,那麼這將致使運行循環退出。附加劇復計時器能保持運行在一個較長的時間週期的運行循環,但將涉及週期性擊發定時器喚醒線程,這其實是輪詢的另外一種形式。與此相反,輸入源等待一個事件的發生,保持你的線程睡着了,直到它。

開始運行循環

開始運行循環只爲您的應用程序中的輔助線程是必要的。一個運行循環必須至少有一個輸入源或定時器來監控。若是一我的尚未安裝,運行循環當即退出。

有幾種方法來啓動運行循環,包括如下狀況:

  • 無條件

  • 有一組時間限制

  • 在一個特定的模式

無條件地輸入您的運行循環是最簡單的選擇,但也是最不理想的。運行您的運行循環無條件放線變爲永久循環,它使您能夠在運行循環自己很難控制。您能夠添加和刪除輸入源和定時器,可是中止運行循環的惟一方法是殺死它。也沒有辦法運行在自定義模式下運行循環。

相反,無條件地運行運行循環的,這是更好地運行具備超時值的運行循環。當您使用超時值,運行循環運行,直到事件到達或在指定的時間。若是一個事件到達,該事件被調度到進行處理的處理程序,而後運行退出循環。而後,您的代碼能夠從新啓動運行循環來處理下一個事件。若是在指定的時間,而不是,你能夠簡單地從新開始運行循環,或者使用時間作任何須要的家政服務。

除了超時值,還可使用特定的模式下運行您的運行循環。模式和超時值並不相互排斥,並開始運行循環時均可以使用。模式限制類型的事件傳遞到運行循環,並在更詳細的描述來源運行循環模式

清單3-2顯示了一個線程的主入口程序的框架版本。本實施例的關鍵部分示出一個運行循環的基本結構。從本質上講,你添加輸入源和定時器的運行循環,而後反覆調用程序的一個開始運行循環。每次運行循環例程返回,檢查時間,看看是否有任何的條件已經出現,可能值得退出線程。該示例使用Core Foundation的運行循環程序,以便它能夠檢查返回結果,並肯定爲何運行循環退出。你也可使用的方法NSRunLoop類,若是您使用的是可能夠相似的方式來運行的運行循環,不須要檢查返回值。(對於一個運行循環調用該方法的一個實例NSRunLoop類,請參閱清單3-14)。

清單3-2   運行運行循環

 - (無效)skeletonThreadMain
{
    //若是不使用垃圾收集設置自動釋放池在這裏。
    BOOL完成= NO;
 
    //你的源代碼或計時器添加到運行循環,並作其餘任何設置。
 
    {
        //開始運行循環,但每一個源處理後返回。
        SINT32結果= CFRunLoopRunInMode(kCFRunLoopDefaultMode,10,YES);
 
        //若是源明確地中止了運行的循環,或若是沒有
        //來源或計時器,繼續和退出。
        若是((結果== kCFRunLoopRunStopped)||(結果== kCFRunLoopRunFinished))
            作= YES;
 
        //檢查任何其餘退出條件在這裏和設置
        //根據須要進行變量。
    }
    而(作!);
 
    //這裏清理代碼。必定要釋聽任何分配的自動釋放池。
}

有可能遞歸地運行一個運行循環。換句話說,你能夠打電話CFRunLoopRunCFRunLoopRunInMode或任何的NSRunLoop方法從輸入源或定時器的處理程序內開始運行循環。這樣作的時候,你能夠用你想要運行的嵌套運行循環,包括使用的模式由外循環運行任意模式。

退出運行循環

有兩種方法可讓運行循環的退出已處理的事件以前:

  • 配置運行循環用的超時值運行。

  • 告訴運行循環中止。

使用超時值確定是首選,若是你能管理它。指定超時值容許運行的循環完成全部的正常處理,包括髮送通知運行循環觀察員飛去。

在明確中止運行循環CFRunLoopStop功能產生相似於超時的結果。運行循環發送任何剩餘的運行循環的通知,而後退出。所不一樣的是,在運行循環你開始無條件您可使用這種技術。

雖然刪除運行循環的輸入源和定時器也可能致使運行循環退出,這不是一個可靠的方式來中止運行循環。一些系統程序輸入源添加到一個運行循環來處理所需的事件。由於你的代碼可能不知道這些輸入源,這將是沒法刪除它們,這將防止運行循環退出。

線程安全和運行循環對象

線程安全取決於你使用的是操縱你的運行循環的API變化。在覈心基礎的功能通常是線程安全的,能夠從任何線程調用。若是您正在執行改變運行循環的配置操做,可是,它仍然是很好的作法,從擁有運行循環儘量線程這樣作。

可可NSRunLoop類不是天生便線程安全做爲其核心基礎對口。若是您使用的是NSRunLoop類來修改你的run loop,你應該讓只擁有該循環運行在同一個線程作。添加輸入源或定時器到屬於不一樣的線程運行的循環可能會致使您的代碼以一種意想不到的方式崩潰或行爲。

配置運行循環來源

如下部分顯示瞭如何設置不一樣類型的兩個可可和核心基礎輸入源的例子。

定義自定義輸入源

建立自定義輸入源包括定義以下:

  • 你想要的信息輸入源來處理。

  • 的調度程序,讓有興趣的客戶知道如何聯繫您的輸入源。

  • 一個處理程序,以執行任何客戶端發送的請求。

  • 一個消除例行程序無效輸入源。

由於你建立自定義輸入源來處理自定義信息,實際配置設計爲靈活。調度,處理和取消程序是你幾乎老是須要爲您的自定義輸入源的關鍵程序。大部分的輸入源的行爲的其他部分,可是,會發生這些處理程序例程的外部。例如,它是由你來定義的機制將數據傳遞到你的輸入源以及輸入源的存在傳達給其餘線程。

圖3-2顯示了自定義輸入源的示例配置。在本實施例中,應用程序的主線程保持到輸入源,該輸入源的自定義命令緩衝器,並在其上安裝輸入源的運行循環引用。當主線程就是了移交給工做線程任務,它與該工做線程啓動任務所需的任何信息一塊兒發佈到命令緩衝區的命令。(由於不管是主線程和工做線程的輸入源能夠訪問命令緩衝區,該訪問必須同步。)一旦命令發佈,主線程信號的輸入源和喚醒工做線程的運行循環。在接收到該喚醒命令,運行循環調用的輸入源,其處理在命令緩衝器中的命令的處理程序。

圖3-2   運行自定義輸入源運行一個自定義輸入源

如下部分說明了從上圖自定義輸入源的落實,並顯示關鍵代碼,你須要實現。

定義輸入源

定義自定義輸入源須要使用Core Foundation的例程來配置你的run loop源並將其鏈接到一個運行循環。雖然基本處理程序是基於C語言的功能,即不從寫爲這些功能包裝和使用Objective-C或C ++來實現你代碼的身體阻止你。

在引入的輸入源圖3-2使用了Objective-C的對象來管理命令緩衝區和運行循環座標。清單3-3顯示了這個對象的定義。在RunLoopSource對象管理命令緩衝區,並使用該緩衝器以接收來自其餘線程的消息。這個清單也顯示了定義RunLoopContext對象,這是真的只是用來傳遞一個容器對象RunLoopSource對象和運行循環參考應用程序的主線程。

清單3-3   自定義輸入源對象定義

@interface RunLoopSource:NSObject的
{
    CFRunLoopSourceRef runLoopSource;
    NSMutableArray裏*命令;
}
 
 - (id)的初始化;
 - (無效)addToCurrentRunLoop;
 - (無效)無效;
 
//處理方法
 - (無效)sourceFired;
 
//登記命令來處理客戶端界面
 - (無效)addCommand:(NSInteger的)命令withData:(ID)的數據;
 - (無效)fireAllCommandsOnRunLoop:(CFRunLoopRef)runloop;
 
@結束
 
//這些是CFRunLoopSourceRef回調函數。
無效RunLoopSourceScheduleRoutine(void *的信息,CFRunLoopRef RL,CFStringRef模式);
無效RunLoopSourcePerformRoutine(void *的信息);
無效RunLoopSourceCancelRoutine(void *的信息,CFRunLoopRef RL,CFStringRef模式);
 
// RunLoopContext是輸入源的註冊過程當中使用的容器對象。
@interface RunLoopContext:NSObject的
{
    CFRunLoopRef runLoop;
    RunLoopSource *源;
}
@屬性(只讀)CFRunLoopRef runLoop;
@屬性(只讀)RunLoopSource *源;
 
 - (ID)initWithSource:(RunLoopSource *)SRC andLoop:(CFRunLoopRef)循環;
@結束

雖然Objective-C代碼管理輸入源的自定義數據,鏈接輸入源到運行循環,須要基於C的回調函數。當你真正重視的運行循環源的運行循環的第一個函數被調用,並在所示列表3-4。由於該輸入源中只有一個客戶機(主線程),它使用調度程序功能將消息發送到其自身與該線程上應用程序的代理註冊。當表明要與輸入源進行通訊,它使用的信息RunLoopContext的對象這樣作。

清單3-4   調度運行循環源

無效RunLoopSourceScheduleRoutine(void *的信息,CFRunLoopRef RL,CFStringRef模式)
{
    RunLoopSource * OBJ =(RunLoopSource *)信息;
    的AppDelegate *德爾= [AppDelegate中sharedAppDelegate]
    RunLoopContext * theContext = [[RunLoopContext頁頭] initWithSource:OBJ andLoop:RL];
 
    [德爾performSelectorOnMainThread:@selector(registerSource :)
                                withObject:theContext waitUntilDone:NO];
}

其中最重要的回調例程的是當用於輸入源發信號處理自數據之一。清單3-5顯示了與相關的執行回調例程RunLoopSource對象。這個函數簡單轉發給作的工做的請求sourceFired方法,而後處理存在的命令緩衝區的任何命令。

清單3-5   中的輸入源執行工做

無效RunLoopSourcePerformRoutine(void *的信息)
{
    RunLoopSource * OBJ =(RunLoopSource *)信息;
    [OBJ sourceFired]
}

若是你從它的運行循環使用刪除輸入源CFRunLoopSourceInvalidate的功能,系統會調用你輸入源的取消例程。你可使用這個程序來通知客戶端輸入信號源再也不有效,他們應該刪除的任何引用。 清單3-6顯示了註冊的取消回調例程RunLoopSource對象。該函數將另外一個RunLoopContext對象的應用程序委託,但此次要求委託刪除運行循環源引用。

清單3-6   做廢輸入源

無效RunLoopSourceCancelRoutine(void *的信息,CFRunLoopRef RL,CFStringRef模式)
{
    RunLoopSource * OBJ =(RunLoopSource *)信息;
    的AppDelegate *德爾= [AppDelegate中sharedAppDelegate]
    RunLoopContext * theContext = [[RunLoopContext頁頭] initWithSource:OBJ andLoop:RL];
 
    [德爾performSelectorOnMainThread:@selector(removeSource :)
                                withObject:theContext waitUntilDone:YES];
}

注:  爲應用程序委託的代碼registerSource:removeSource:方法中顯示與輸入源的客戶協調

 

安裝在運行循環的輸入源

清單3-7顯示了initaddToCurrentRunLoop該方法的RunLoopSource類。該init方法建立CFRunLoopSourceRef了必須實際鏈接到運行循環不透明的類型。它經過RunLoopSource使回調例程有一個指針的對象物體自己做爲上下文信息。直到工做線程調用不會發生輸入源的安裝addToCurrentRunLoop方法,在這一點的RunLoopSourceScheduleRoutine回調函數被調用。一旦輸入源被添加到運行循環,線程能夠運行它的運行循環等待就能夠了。

清單3-7   安裝運行循環源

 - (ID)的init
{
    CFRunLoopSourceContext背景= {0,自我,NULL,NULL,NULL,NULL,NULL,
                                        &RunLoopSourceScheduleRoutine,
                                        RunLoopSourceCancelRoutine,
                                        RunLoopSourcePerformRoutine};
 
    runLoopSource = CFRunLoopSourceCreate(NULL,0,和背景);
    命令= [[NSMutableArray裏的alloc]初始化];
 
    返回自我;
}
 
 - (無效)addToCurrentRunLoop
{
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFRunLoopAddSource(runLoop,runLoopSource,kCFRunLoopDefaultMode);
}

與輸入源的客戶協調

爲了您的輸入源是有用的,你須要處理它,並從另外一個線程表示它。輸入源的所有要點是將其相關的線程休眠,直到有事可作。這一事實就必須在你的應用程序的其餘線程知道該輸入源,並有辦法與之通訊。

通知客戶關於輸入源的方法之一是發出註冊請求時,第一次安裝在其運行的循環輸入源。您能夠根據須要輸入信號源,與儘量多的客戶登記,或者你能夠簡單地用一些中央機構,而後VENDS輸入源爲有興趣的客戶註冊。清單3-8顯示了應用委託定義,當調用註冊方法RunLoopSource對象的調度函數被調用。此方法接收RunLoopContext由提供的對象RunLoopSource對象並將其添加到它的源列表。這個清單也顯示了使用時,從它的運行循環中刪除註銷輸入源的程序。

清單3-8   註冊和刪除與應用程序委託的輸入源

 - (無效)registerSource:(RunLoopContext *)sourceInfo;
{
    [sourcesToPing ADDOBJECT:sourceInfo];
}
 
 - (無效)removeSource:(RunLoopContext *)sourceInfo
{
    ID objToRemove =零;
 
    對於(RunLoopContext *在sourcesToPing上下文)
    {
        若是([背景下,isEqual:方法sourceInfo])
        {
            objToRemove =背景;
            打破;
        }
    }
 
    若是(objToRemove)
        [sourcesToPing的removeObject:objToRemove];
}

注:  調用在前面的上市方法的回調函數在顯示列表3-4列表3-6

 

信號輸入源

它的數據後雙手關閉輸入源,客戶端必須信號源和喚醒它的運行循環。信號源讓運行循環知道源已準備好進行處理。並且由於當信號發生線程多是睡着了,你應該老是醒來運行循環明確。若是不這樣作可能會致使在處理輸入源的延遲。

清單3-9顯示了fireCommandsOnRunLoop該方法的RunLoopSource對象。當他們準備好源來處理它們添加到緩衝區中的命令的客戶端調用此方法。

清單3-9   醒來的運行循環

 - (無效)fireCommandsOnRunLoop:(CFRunLoopRef)runloop
{
    CFRunLoopSourceSignal(runLoopSource);
    CFRunLoopWakeUp(runloop);
}

注意:  你不該該試圖處理SIGHUP經過短信定製輸入源或其餘類型的進程級的信號。核心基礎功能喚醒運行循環不是信號安全的,不該該應用程序的信號處理程序內部使用。有關信號處理例程的詳細信息,請參閱sigaction手冊頁。

 

配置定時器源

要建立一個定時器源,全部你所要作的就是建立一個定時器對象,並安排它在你的運行循環。在可可,你可使用NSTimer類來建立新的Timer對象,並在核心基金使用CFRunLoopTimerRef不透明的類型。在內部,NSTimer該類是核心基礎的擴展,提供了一些方便的功能,如建立和使用相同的方法安排一個計時器的能力。

在可可,你能夠建立並安排一次所有使用如下任一類方法的計時器:

這些方法建立定時器,它在默認模式下添加到當前線程的運行循環(NSDefaultRunLoopMode)。您也能夠手動,若是你想經過建立調度計時器NSTimer對象,而後用它添加到運行循環addTimer:forMode:的方法NSRunLoop。這兩種技術基本上作一樣的事情,但給你不一樣級別的定時器的配置控制。例如,若是你建立定時器,並手動添加到運行循環,你能夠這樣作使用默認模式之外的模式。清單3-10顯示瞭如何建立使用兩種技術定時器。第必定時器具備1秒的初始延遲,但在此以後,每0.1秒按期觸發。第二個定時器開始初始0.2秒延遲後燒製,再通過每0.2秒閃光。

清單3-10   使用的NSTimer建立和調度計時器

NSRunLoop * myRunLoop = [NSRunLoop currentRunLoop]
 
//建立並安排第一個定時器。
*的NSDate = futureDate [NSDate的dateWithTimeIntervalSinceNow:1.0];
*的NSTimer = myTimer [的NSTimer頁頭] initWithFireDate:futureDate
                        區間:0.1
                        目標:自我
                        選擇:@選擇(myDoFireTimer1 :)
                        用戶信息:無
                        重複:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
 
//建立並安排第二計時器。
[的NSTimer scheduledTimerWithTimeInterval:0.2
                        目標:自我
                        選擇:@選擇(myDoFireTimer2 :)
                        用戶信息:無
                        重複:YES];

清單3-11顯示了配置使用的Core Foundation功能的計時器所需的代碼。雖然這個例子沒有經過在上下文結構中的任何用戶定義的信息,你可使用這個結構來繞過你須要爲您的計時器任何自定義的數據。有關此結構的內容的更多信息,請參閱其在描述CFRunLoopTimer參考

清單3-11   建立和調度使用Core Foundation的一個計時器

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext上下文= {0,NULL,NULL,NULL,NULL};
CFRunLoopTimerRef計時器= CFRunLoopTimerCreate(kCFAllocatorDefault,0.1,0.3,0,0,
                                        &myCFTimerCallback,與背景);
 
CFRunLoopAddTimer(runLoop,計時器,kCFRunLoopCommonModes);

配置基於端口的輸入源

不管可可和核心基金會線程之間或進程之間的通訊提供了基於端口的對象。如下部分顯示您如何設置使用幾種不一樣類型的端口的端口進行通訊。

配置NSMachPort對象

要創建與本地鏈接NSMachPort對象,建立端口對象並將其添加到您的主線程的運行循環。當您啓動輔助線程,傳遞同一個對象的線程的入口點函數。輔助線程可使用相同的對象發送消息回主線程。

實現主線程代碼

清單3-12顯示了啓動輔助工做線程的主線程代碼。由於Cocoa框架執行許多配置端口和運行循環的中間步驟,該launchThread方法是明顯比它的核心基礎當量(短清單3-17); 可是,二者的行爲幾乎是相同的。一個區別是,而不是發送本地端口的名稱工做線程的,這種方法發送NSPort直接對象。

清單3-12   主線程啓動方法

 - (無效)launchThread
{
    * NSPORT MyPort上= [NSMachPort端口]
    若是(MyPort上)
    {
        //這個類處理傳入端口的消息。
        [MyPort上setDelegate:個體經營];
 
        //安裝端口做爲當前的運行循環的輸入源。
        [NSRunLoop currentRunLoop] addPort:MyPort上forMode:NSDefaultRunLoopMode];
 
        //分離線程。讓員工釋放的端口。
        [NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort :)
               toTarget:[MyWorkerClass類] withObject:MyPort上]。
    }
}

爲了創建你的線程之間的雙向通訊通道,您可能想工做者線程發送本身的本地端口到主線程在辦理入住手續的消息。接到辦理入住手續的消息讓你的主線程知道全部在啓動第二個線程順利,也給你一個方法來發送更多的信息給該線程。

清單3-13顯示了handlePortMessage:對主線程的方法。當數據到達該線程自身的本地端口上調用此方法。當入住消息到達時,該方法直接從端口信息檢索的端口輔助線程並將其保存以備後用。

清單3-13   處理Mach端口的消息

#定義kCheckinMessage 100
 
//從工做者線程處理響應。
 - (無效)handlePortMessage:(NSPortMessage *)portMessage
{
    無符號整型消息= [portMessage MSGID]
    NSPORT * distantPort =零;
 
    若是(消息== kCheckinMessage)
    {
        //獲取工做線程的通訊端口。
        distantPort = [portMessage發送端口]。
 
        //保留並保存工人端口供之後使用。
        [個體經營storeDistantPort:distantPort];
    }
    其餘
    {
        //處理其餘消息。
    }
}

實施輔助線程代碼

對於輔助工做線程,必須配置線程並使用指定的端口回信息傳達給主線程。

清單3-14示出了用於設置輔助線程的代碼。爲線程建立自動釋放池後,該方法將建立一個工人對象驅動線程執行。工人對象的sendCheckinMessage:方法(在顯示清單3-15)建立工做線程的本地端口併發送入住消息回主線程。

清單3-14   啓動使用馬赫端口的工做線程

+(無效)LaunchThreadWithPort:(ID)INDATA
{
    NSAutoreleasePool *池= [[NSAutoreleasePool的alloc]初始化];
 
    //設置該線程和主線程之間的鏈接。
    NSPORT * distantPort =(NSPORT *)INDATA;
 
    MyWorkerClass * workerObj = [[自個人alloc]初始化];
    [workerObj sendCheckinMessage:distantPort];
    [distantPort發佈]
 
    //讓運行循環過程的東西。
    {
        [NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                            beforeDate:[的NSDate distantFuture]];
    }
    而([workerObj shouldExit]!);
 
    [workerObj發佈]
    [池釋放];
}

當使用NSMachPort,本地和遠程線程能夠用於線程之間的單向通訊的相同端口對象。換句話說,由一個線程建立的本地端口對象成爲其餘線程遠程端口對象。

清單3-15顯示了輔助線程的入住程序。此方法設置爲將來通訊自身的本地端口,而後發送辦理入住手續的消息回主線程。該方法使用在接收到的端口對象LaunchThreadWithPort:方法做爲消息的目標。

清單3-15   發送使用馬赫端口的入住信息

//工做線程辦理入住手續的方法
 - (無效)sendCheckinMessage:(NSPORT *)外港
{
    //保留並保存遠程端口以便未來使用。
    [個體經營setRemotePort:外港];
 
    //建立並配置工做線程端口。
    * NSPORT MyPort上= [NSMachPort端口]
    [MyPort上setDelegate:個體經營];
    [NSRunLoop currentRunLoop] addPort:MyPort上forMode:NSDefaultRunLoopMode];
 
    //建立辦理入住手續的消息。
    NSPortMessage * messageObj = [[NSPortMessage頁頭] initWithSendPort:外港
                                         receivePort:MyPort上的組件:無];
 
    若是(messageObj)
    {
        //完成配置信息,並當即將其發送。
        [messageObj setMsgId:setMsgid:kCheckinMessage];
        [messageObj sendBeforeDate:[NSDate的日期]];
    }
}

配置NSMessagePort對象

要創建與本地鏈接NSMessagePort對象,你不能簡單地在線程之間傳遞端口對象。遠程消息端口必須經過名字來得到。在可可以使這可能須要具備特定名稱註冊的本地端口,而後經過該名稱遠程線程使其可以得到適當的端口對象進行通訊。清單3-16顯示了要使用消息的端口狀況下,端口建立和註冊過程。

清單3-16   註冊信息的端口

NSPORT *將localPort = [[NSMessagePort的alloc]初始化];
 
//配置對象,並將其添加到當前運行的循環。
[將localPort setDelegate:個體經營];
[NSRunLoop currentRunLoop] addPort:將localPort forMode:NSDefaultRunLoopMode];
 
//註冊使用特定名稱的端口。該名稱必須是惟一的。
* NSString的localPortName = [的NSString stringWithFormat:@「MyPortName」];
[NSMessagePortNameServer sharedInstance] registerPort:將localPort
                     名稱:localPortName];

在配置Core Foundation的基於端口的輸入源

本節將展現如何設置使用Core Foundation的應用程序的主線程和輔助線程之間的雙向通訊通道

清單3-17顯示了應用程序的主線程調用,以啓動工做線程的代碼。代碼作的第一件事就是創建一個CFMessagePortRef不透明類型來監聽來自工做線程的消息。工做線程須要端口的名稱來進行鏈接,以使字符串值被輸送到工做線程的入口點的功能。端口名稱通常應爲當前用戶上下文中是惟一的; 不然,你可能會遇到衝突。

清單3-17   附加一個核心基礎信息端口一個新的線程

#定義kThreadStackSize(8 * 4096)
 
OSStatus MySpawnThread()
{
    //用於接收響應建立一個本地端口。
    CFStringRef myPortName;
    CFMessagePortRef MyPort上;
    CFRunLoopSourceRef rlSource;
    CFMessagePortContext上下文= {0,NULL,NULL,NULL,NULL};
    布爾shouldFreeInfo;
 
    //建立與港口名稱的字符串。
    myPortName = CFStringCreateWithFormat(NULL,NULL,CFSTR(「com.myapp.MainThread」));
 
    //建立的端口。
    MyPort上= CFMessagePortCreateLocal(NULL,
                myPortName,
                &MainThreadResponseHandler,
                和背景,
                &shouldFreeInfo);
 
    若是(MyPort上!= NULL)
    {
        //端口建立成功。
        //如今爲它建立一個運行循環源。
        rlSource = CFMessagePortCreateRunLoopSource(NULL,MyPort上,0);
 
        若是(rlSource)
        {
            //源添加到當前的運行循環。
            CFRunLoopAddSource(CFRunLoopGetCurrent(),rlSource,kCFRunLoopDefaultMode);
 
            //一旦安裝完畢,這些能夠被釋放。
            CFRelease(MyPort上);
            CFRelease(rlSource);
        }
    }
 
    //建立線程並繼續處理。
    MPTaskID TASKID;
    回報(MPCreateTask(ServerThreadEntryPoint,
                    (無效*)myPortName,
                    kThreadStackSize,
                    空值,
                    空值,
                    空值,
                    0,
                    &的TaskID));
}

若是安裝了端口並啓動線程,在等待的線程來檢查主線程能夠繼續其正常運行。當入住消息到達時,它被分派到主線程的MainThreadResponseHandler功能,顯示清單3-18。這個函數提取端口名工做線程,併爲將來通訊的管道。

清單3-18   接收簽入的消息

#定義kCheckinMessage 100
 
//主線程端口消息處理程序
CFDataRef MainThreadResponseHandler(CFMessagePortRef地方,
                    SINT32 MSGID,
                    CFDataRef數據,
                    void *的信息)
{
    若是(MSGID == kCheckinMessage)
    {
        CFMessagePortRef messagePort;
        CFStringRef threadPortName;
        CFIndex BufferLength中= CFDataGetLength(數據);
        UINT8 *緩衝區= CFAllocatorAllocate(NULL,BufferLength中,0);
 
        CFDataGetBytes(數據,CFRangeMake(0,BufferLength中),緩衝液);
        threadPortName = CFStringCreateWithBytes(NULL,緩衝,BufferLength中,kCFStringEncodingASCII,FALSE);
 
        //你必須按名稱得到遠程消息端口。
        messagePort = CFMessagePortCreateRemote(NULL,(CFStringRef)threadPortName);
 
        若是(messagePort)
        {
            //保留並保存線程的通訊端口以備未來參考。
            AddPortToListOfActiveThreads(messagePort);
 
            //因爲港口是由先前的功能,保留的釋放
            // 在這裏。
            CFRelease(messagePort);
        }
 
        // 清理。
        CFRelease(threadPortName);
        CFAllocatorDeallocate(NULL,緩衝區);
    }
    其餘
    {
        //處理其餘消息。
    }
 
    返回NULL;
}

隨着配置的主線,剩下的惟一事情是新建立的工做線程建立本身的端口,並辦理入住手續。清單3-19顯示了工做線程的入口點函數。該函數提取主線程的端口名稱,並用它來建立一個遠程鏈接回主線程。該函數而後爲本身建立一個本地端口,安裝在線程的運行循環的端口,併發送辦理入住手續的信息,即包括本地端口名稱的主線。

清單3-19   設置線程結構

OSStatus ServerThreadEntryPoint(void *的參數)
{
    //建立遠程端口到主線程。
    CFMessagePortRef mainThreadPort;
    CFStringRef PORTNAME =(CFStringRef)PARAM;
 
    mainThreadPort = CFMessagePortCreateRemote(NULL,PORTNAME);
 
    //不含在參數傳遞的字符串。
    CFRelease(PORTNAME);
 
    //工做線程建立的端口。
    CFStringRef myPortName = CFStringCreateWithFormat(NULL,NULL,CFSTR(「com.MyApp.Thread-%D」),MPCurrentTaskID());
 
    //存儲端口,供往後參考此線程的上下文信息。
    CFMessagePortContext背景= {0,mainThreadPort,NULL,NULL,NULL};
    布爾shouldFreeInfo;
    布爾shouldAbort = TRUE;
 
    CFMessagePortRef MyPort上= CFMessagePortCreateLocal(NULL,
                myPortName,
                &ProcessClientRequest,
                和背景,
                &shouldFreeInfo);
 
    若是(shouldFreeInfo)
    {
        //沒法建立本地端口,因此殺死線程。
        MPExit(0);
    }
 
    CFRunLoopSourceRef rlSource = CFMessagePortCreateRunLoopSource(NULL,MyPort上,0);
    若是(!rlSource)
    {
        //沒法建立本地端口,因此殺死線程。
        MPExit(0);
    }
 
    //源添加到當前的運行循環。
    CFRunLoopAddSource(CFRunLoopGetCurrent(),rlSource,kCFRunLoopDefaultMode);
 
    //一旦安裝完畢,這些能夠被釋放。
    CFRelease(MyPort上);
    CFRelease(rlSource);
 
    //打包端口名稱和發送校驗的消息。
    CFDataRef returnData =零;
    CFDataRef outData;
    CFIndex stringLength = CFStringGetLength(myPortName);
    UINT8 *緩衝液= CFAllocatorAllocate(NULL,stringLength,0);
 
    CFStringGetBytes(myPortName,
                CFRangeMake(0,stringLength),
                kCFStringEncodingASCII,
                0,
                假,
                緩衝,
                stringLength,
                空值);
 
    outData = CFDataCreate(NULL,緩衝器,stringLength);
 
    CFMessagePortSendRequest(mainThreadPort,kCheckinMessage,outData,0.1,0.0,NULL,NULL);
 
    //清理線程的數據結構。
    CFRelease(outData)以;
    CFAllocatorDeallocate(NULL,緩衝區);
 
    //輸入運行循環。
    CFRunLoopRun();
}

一旦進入它的運行循環,發送到線程的端口全部將來事件被處理ProcessClientRequest功能。該功能的實現依賴於工做線程作,而不是這裏顯示的類型。

相關文章
相關標籤/搜索