線程編程指南翻譯第三篇(運行循環(huán))

文檔地址

案例代碼下載

運行循環(huán)

運行循環(huán)是與線程相關的基礎架構的一部分。一個運行循環(huán)是指用于安排工作,并協調接收傳入事件的事件處理循環(huán)。運行循環(huán)的目的是在有任務時保持線程忙,并在沒有任務時讓線程進入休眠狀態(tài)。

運行循環(huán)管理不是完全自動的。仍然必須設計線程的代碼以在適當的時間啟動運行循環(huán)并響應傳入的事件。Cocoa和Core Foundation都提供了運行循環(huán)對象來幫助配置和管理線程的運行循環(huán)。應用程序不需要顯式創(chuàng)建這些對象; 每個線程(包括應用程序的主線程)都有一個關聯的運行循環(huán)對象。但是,只有輔助線程需要顯式運行其運行循環(huán)。作為應用程序啟動過程的一部分,應用程序框架會自動在主線程上設置并運行運行循環(huán)。

以下部分提供有關運行循環(huán)以及如何為應用程序配置它們的更多信息。有關運行循環(huán)對象的其他信息,請參閱NSRunLoop類參考CFRunLoop參考。

運行循環(huán)剖析

運行循環(huán)非常像它的名字。它是線程進入并用于運行事件處理程序以響應傳入事件的循環(huán)。代碼提供了用于實現運行循環(huán)的實際循環(huán)部分的控制語句 - 換句話說,代碼提供了驅動運行循環(huán)的while或者for循環(huán)。在循環(huán)中,使用運行循環(huán)對象來“運行”事件處理代碼來接收事件并調用已安裝的處理程序。

運行循環(huán)從兩種不同類型的源接收事件。輸入源提供異步事件,通常是來自另一個線程或來自不同應用程序的消息。定時器源提供同步事件,發(fā)生在預定時間或重復間隔。兩種類型的源都使用特定于應用程序的處理程序例程來處理事件。

圖3-1顯示了運行循環(huán)和各種源的概念結構。輸入源將異步事件傳遞給相應的處理程序,并導致該runUntilDate:方法(在線程的關聯NSRunLoop對象上調用)退出。計時器源將事件傳遞給其處理程序例程,但不會導致運行循環(huán)退出。

圖3-1 運行循環(huán)的結構及其來源

image

除了處理輸入源之外,運行循環(huán)還會生成有關運行循環(huán)行為的通知。已注冊的運行循環(huán)觀察器可以接收這些通知并使用它們在線程上執(zhí)行其他處理??梢允褂肅ore Foundation在線程上安裝運行循環(huán)觀察器。

以下部分提供有關運行循環(huán)的組件及其運行模式的更多信息。還描述了在處理事件期間的不同時間生成的通知。

運行循環(huán)模式

一個運行循環(huán)模式是監(jiān)控輸入源和定時器源的集合,也是運行循環(huán)被通知的觀察者的集合。每次運行運行循環(huán)時,都指定(顯式或隱式)運行的特定“模式”。在運行循環(huán)的過程中,僅監(jiān)視與該模式關聯的源并允許其傳遞其事件。(類似地,只有與該模式相關聯的觀察者被通知運行循環(huán)的進度。)與其他模式相關聯任何新事件都會掛起,直到后續(xù)以適當模式通過循環(huán)。

代碼中,可以按名稱識別模式。Cocoa和Core Foundation都定義了默認模式和幾種常用模式,以及用于在代碼中指定這些模式的字符串。只需為模式名稱指定自定義字符串即可定義自定義模式。雖然為自定義模式指定的名稱是任意的,但這些模式的內容不是。必須確保將一個或多個輸入源,計時器或運行循環(huán)觀察器添加到創(chuàng)建的模式中才有用。

可以使用模式在特定的運行循環(huán)期間過濾掉不需要的來源中的事件。大多數情況下,需要在系統定義的“默認”模式下運行運行循環(huán)。但是,模態(tài)面板可能以“模態(tài)”模式運行。在此模式下,只有與模態(tài)面板相關的源才會向線程傳遞事件。對于輔助線程,可以使用自定義模式來防止低優(yōu)先級源在時間關鍵操作期間傳遞事件。

注意: 模式根據事件的源而不是事件的類型進行區(qū)分。例如,不會使用模式僅匹配鼠標按下事件或僅匹配鍵盤事件。您可以使用模式來偵聽不同的端口集,暫時掛起計時器,或源和當前正被監(jiān)視的運行循環(huán)觀察者的其他變化。

表3-1列出了Cocoa和Core Foundation定義的標準模式以及何時使用該模式的說明。name列列出了用于在代碼中指定模式的實際常量。

表3-1 預定義的運行循環(huán)模式

模式 名稱 描述
默認 NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation) 默認模式是用于大多數操作。大多數情況下,應該使用此模式啟動運行循環(huán)并配置輸入源。
連接 NSConnectionReplyMode (Cocoa) Cocoa將此模式與NSConnection對象結合使用以監(jiān)視回復。應該很少需要使用此模式。
模態(tài) NSModalPanelRunLoopMode (Cocoa) Cocoa使用此模式來識別用于模態(tài)面板的事件。
事件跟蹤 NSEventTrackingRunLoopMode (Cocoa) Cocoa使用此模式在鼠標拖動循環(huán)和其他種類的用戶界面跟蹤循環(huán)期間限制傳入事件。
常用模式 NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation) 這是一組可配置的常用模式。將輸入源與此模式相關聯也會將其與組中的每個模式相關聯。對于Cocoa應用程序,此集合默認包括默認,模態(tài)和事件跟蹤模式。Core Foundation最初只包含默認模式??梢允褂迷揅FRunLoopAddCommonMode功能將自定義模式添加到集合中。

輸入源

輸入源以異步方式向線程傳遞事件。事件的來源取決于輸入源的類型,通常是兩個類別中的一個。基于端口的輸入源監(jiān)視應用程序的Mach端口。自定義輸入源監(jiān)視自定義事件源。就運行循環(huán)而言,輸入源是基于端口還是自定義無關緊要。系統只是實現可以使用的兩種類型的輸入源。兩個來源之間的唯一區(qū)別是它們如何發(fā)出信號?;诙丝诘脑从蓛群俗詣影l(fā)出信號,而自定義源必須從另一個線程手動發(fā)信號。

創(chuàng)建輸入源時,將其分配給運行循環(huán)的一個或多個模式。模式會影響在任何給定時刻輸入源是否被監(jiān)聽。大多數情況下,在默認模式下運行運行循環(huán),但也可以指定自定義模式。如果輸入源未處于當前監(jiān)視模式,則會生成的任何事件都會掛起,直到運行循環(huán)以正確模式運行。

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

基于端口的源

Cocoa和Core Foundation提供內置支持,使用與端口相關的對象和函數創(chuàng)建基于端口的輸入源。例如,在Cocoa中,根本不必直接創(chuàng)建輸入源。只需創(chuàng)建一個端口對象,并使用NSPort方法將該端口添加到運行循環(huán)中。port對象處理所需輸入源的創(chuàng)建和配置。

在Core Foundation中,您必須手動創(chuàng)建端口及其運行循環(huán)源。在這兩種情況下,使用函數關聯的端口不透明類型(CFMachPortRef,CFMessagePortRef或CFSocketRef)創(chuàng)建合適的對象。

有關如何設置和配置基于端口的自定義源的示例,請參閱配置基于端口的輸入源。

自定義輸入源

要創(chuàng)建自定義輸入源,在Core Foundation中必須使用與CFRunLoopSourceRef的opaque類型關聯的函數??梢允褂枚鄠€回調函數配置自定義輸入源。Core Foundation在不同的點調用這些函數來配置源,處理傳入事件,并在從運行循環(huán)中刪除源時銷毀源。

除了在事件到達時定義自定義源的行為外,還必須定義事件傳遞機制。源的這一部分在一個單獨的線程上運行,負責為輸入源提供其數據,并在數據準備好進行處理時發(fā)出信號。事件傳遞機制取決于您,但不必過于復雜。

有關如何創(chuàng)建自定義輸入源的示例,請參閱定義自定義輸入源。有關自定義輸入源的參考信息,另請參閱CFRunLoopSource參考。

Cocoa執(zhí)行選擇器源

除了基于端口的源,Cocoa還定義了一個自定義輸入源,允許在任何線程上執(zhí)行選擇器。與基于端口的源類似,執(zhí)行選擇器請求在目標線程上被序列化,從而減輕了在一個線程上運行多個方法時可能發(fā)生的許多同步問題。與基于端口的源不同,執(zhí)行選擇器源在執(zhí)行其選擇器后將其自身從運行循環(huán)中移除。

注意: 在OS X v10.5之前,執(zhí)行選擇器源主要用于向主線程發(fā)送消息,但在OS X v10.5及更高版本和iOS中,您可以使用它們將消息發(fā)送到任何線程。

在另一個線程上執(zhí)行選擇器時,目標線程必須具有活動的運行循環(huán)。對于您創(chuàng)建的線程,這意味著要等到代碼顯式啟動運行循環(huán)。但是,因為主線程啟動了自己的運行循環(huán),所以只要應用程序調用applicationDidFinishLaunching:應用程序委托的方法,就可以開始在該線程上發(fā)出調用 。運行循環(huán)每次通過循環(huán)處理所有排隊的執(zhí)行選擇器調用,而不是在每次循環(huán)迭代期間處理一個。

表3-2列出了定義在NSObject可用于在其他線程上執(zhí)行選擇器的方法。因為NSObject聲明了這些方法,所以您可以在任何可以訪問Objective-C對象的線程中使用它們,包括POSIX線程。這些方法實際上并不創(chuàng)建新線程來執(zhí)行選擇器。

表3-2 在其他線程上執(zhí)行選擇器

方法 描述
performSelectorOnMainThread:withObject:waitUntilDone:與performSelectorOnMainThread:withObject:waitUntilDone:modes: 在該線程的下一個運行循環(huán)周期中,在應用程序的主線程上執(zhí)行指定的選擇器。這些方法為您提供了阻止當前線程直到執(zhí)行選擇器的選擇。
performSelector:onThread:withObject:waitUntilDone:與performSelector:onThread:withObject:waitUntilDone:modes: 在您擁有NSThread對象的任何線程上執(zhí)行指定的選擇器。這些方法為您提供了阻止當前線程直到執(zhí)行選擇器的選擇。
performSelector:withObject:afterDelay:與performSelector:withObject:afterDelay:inModes: 在下一個運行循環(huán)周期的可選延遲時間之后,在當前線程上執(zhí)行指定的選擇器。因為它等待直到下一個運行循環(huán)周期來執(zhí)行選擇器,所以這些方法提供了來自當前執(zhí)行代碼的自動延遲。多個排隊選擇器按排隊順序依次執(zhí)行。
cancelPreviousPerformRequestsWithTarget:與cancelPreviousPerformRequestsWithTarget:selector:object: 允許您取消使用performSelector:withObject:afterDelay:or performSelector:withObject:afterDelay:inModes:方法發(fā)送到當前線程的消息。

有關每種方法的詳細信息,請參閱NSObject類參考。

定時器源

定時器源在將來的預設時間將事件同步傳遞給您的線程。定時器是線程通知自己做某事的一種方式。例如,一旦在來自用戶的連續(xù)擊鍵之間經過了一定量的時間,搜索框就可以使用計時器來啟動自動搜索。使用此延遲時間使用戶有機會在開始搜索之前輸入盡可能多的所需搜索字符串。

雖然它生成基于時間的通知,但計時器不是實時機制。與輸入源類似,定時器與運行循環(huán)的特定模式相關聯。如果計時器未處于運行循環(huán)當前正在監(jiān)視的模式,則在您以其中一個計時器支持的模式運行運行循環(huán)之前,它不會觸發(fā)。類似地,如果計時器在運行循環(huán)處于執(zhí)行處理程序例程的過程中觸發(fā),則計時器將等待直到下一次通過運行循環(huán)來調用其處理程序例程。如果運行循環(huán)根本沒有運行,則計時器永遠不會觸發(fā)。

您可以將計時器配置為僅生成一次或重復生成事件。重復計時器根據計劃的觸發(fā)時間自動重新計劃,而不是實際的觸發(fā)時間。例如,如果計劃在特定時間和之后每5秒計觸發(fā)一次計時器,則即使實際發(fā)射時間延遲,計劃發(fā)射時間也將始終落在原始的5秒時間間隔上。如果發(fā)射時間延遲太多以至于錯過了一個或多個預定發(fā)射時間,則計時器僅在錯過的時間段內發(fā)射一次。在錯過的時間內觸發(fā)后,計時器被重新安排用于下一個預定的觸發(fā)時間。

有關配置定時器源的更多信息,請參閱配置定時器源。有關參考信息,請參閱NSTimer類參考CFRunLoopTimer參考。

運行循環(huán)觀察器

與在發(fā)生適當的異步或同步事件時觸發(fā)的源相反,運行循環(huán)觀察器在執(zhí)行運行循環(huán)期間在特殊位置觸發(fā)。您可以使用運行循環(huán)觀察器來準備線程以處理給定事件或在線程進入休眠狀態(tài)之前準備線程。您可以將運行循環(huán)觀察器與運行循環(huán)中的以下事件相關聯:

運行循環(huán)的入口。
當運行循環(huán)即將處理計時器時。
當運行循環(huán)即將處理輸入源時。
當運行循環(huán)即將進入睡眠狀態(tài)時。
當運行循環(huán)喚醒時,但在它處理喚醒它的事件之前。
從運行循環(huán)退出。
您可以使用Core Foundation將運行循環(huán)觀察器添加到應用程序。要創(chuàng)建運行循環(huán)觀察器,請創(chuàng)建CFRunLoopObserverRefopaque類型的新實例。此類型會跟蹤您的自定義回調函數及其感興趣的活動。

與計時器類似,運行循環(huán)觀察器可以使用一次或重復使用。一次性觀察者在發(fā)射后將其自身從運行循環(huán)中移除,而重復的觀察者仍然附著。您可以指定觀察者在創(chuàng)建時運行一次還是重復運行。

有關如何創(chuàng)建運行循環(huán)觀察器的示例,請參閱配置運行循環(huán)。有關參考信息,請參閱CFRunLoopObserver參考。

運行循環(huán)事件序列

每次運行它時,線程的運行循環(huán)都會處理掛起的事件,并為任何附加的觀察者生成通知。它執(zhí)行此操作的順序非常具體,如下所示:

  1. 通知觀察者已經輸入了運行循環(huán)。
  2. 通知觀察者準備好的計時器即將觸發(fā)。
  3. 通知觀察者任何非基于端口的輸入源即將觸發(fā)。
  4. 觸發(fā)任何準備觸發(fā)的基于非端口的輸入源。
  5. 如果基于端口的輸入源準備就緒并等待觸發(fā),請立即處理該事件。轉到第9步。
  6. 通知觀察者線程即將睡眠。
  7. 將線程置于睡眠狀態(tài),直到發(fā)生以下事件之一:
  • 基于端口的輸入源事件到達。
  • 計時器觸發(fā)。
  • 為運行循環(huán)設置的超時值到期。
  • 運行循環(huán)被明確喚醒。
  1. 通知觀察者線程剛剛醒來。
  2. 處理待處理事件。
  • 如果觸發(fā)了用戶定義的計時器,則處理計時器事件并重新啟動循環(huán)。轉到第2步。
  • 如果輸入源被觸發(fā),則傳遞事件。
  • 如果運行循環(huán)被明確喚醒但尚未超時,請重新啟動循環(huán)。轉到第2步。
  1. 通知觀察者運行循環(huán)已退出。

由于計時器和輸入源的觀察者通知是在這些事件實際發(fā)生之前傳遞的,因此通知時間與實際事件的時間之間可能存在差距。如果這些事件之間的時間關系很重要,您可以使用睡眠和喚醒睡眠通知來幫助您關聯實際事件之間的時間。

因為在運行運行循環(huán)時會傳遞計時器和其他定期事件,所以繞過該循環(huán)會中斷這些事件的傳遞。每當您通過輸入循環(huán)并重復從應用程序請求事件來實現鼠標跟蹤例程時,就會出現此行為的典型示例。因為您的代碼直接抓取事件,而不是讓應用程序正常調度這些事件,所以直到在鼠標跟蹤例程退出并將控制權返回給應用程序,活動計時器將無法觸發(fā)。

可以使用運行循環(huán)對象顯式喚醒運行循環(huán)。其他事件也可能導致運行循環(huán)被喚醒。例如,添加另一個非基于端口的輸入源會喚醒運行循環(huán),以便可以立即處理輸入源,而不是等到其他事件發(fā)生。

什么時候使用運行循環(huán)?

唯一需要顯式運行運行循環(huán)的是為應用程序創(chuàng)建輔助線程。應用程序主線程的運行循環(huán)是一個至關重要的基礎架構。因此,應用程序框架提供了運行主應用程序循環(huán)的代碼并自動啟動該循環(huán)。所述在IOS中UIApplication(或在OS X中NSApplication)的run方法啟動應用程序的主循環(huán)作為正常啟動序列的一部分。如果您使用Xcode模板項目來創(chuàng)建應用程序,則永遠不必顯式調用這些例程。

對于輔助線程,您需要確定是否需要運行循環(huán),如果是,則自行配置并啟動它。在有的情況下,不需要啟動線程的運行循環(huán)。例如,如果使用線程執(zhí)行某些長時間運行且預定義的任務,則可以避免啟動運行循環(huán)。運行循環(huán)適用于您希望與線程進行更多交互的情況。例如,如果您計劃執(zhí)行以下任何操作,則需要啟動運行循環(huán):

  • 使用端口或自定義輸入源與其他線程通信。
  • 在線程上使用計時器。
  • 應用程序中的任何使用performSelectorCocoa...方法。
  • 保持線程以執(zhí)行定期任務。

如果您確實選擇使用運行循環(huán),則配置和設置非常簡單。與所有線程編程一樣,您應該有一個在適當情況下退出輔助線程的計劃。最好通過讓退出而不是強制終止來干凈地結束一個線程。有關如何配置和退出運行循環(huán)的信息,請參閱使用運行循環(huán)對象。

使用運行循環(huán)對象

運行循環(huán)對象提供了用于將輸入源,計時器和運行循環(huán)觀察器添加到運行循環(huán)然后運行它的主界面。每個線程都有一個與之關聯的運行循環(huán)對象。在Cocoa中,此對象是NSRunLoop類的實例。在低級應用程序中,它是指向CFRunLoopRefopaque類型的指針。

獲取運行循環(huán)對象

要獲取當前線程的運行循環(huán),請使用以下方法之一:

  • 在Cocoa應用程序中,使用NSRunLoop的currentRunLoop類方法來檢索NSRunLoop對象。
  • 使用該CFRunLoopGetCurrent功能。
    雖然它們不是免費的橋接類型,但您可以在需要CFRunLoopRef不透明類型時從NSRunLoop對象獲取。NSRunLoop類定義了一個getCFRunLoop返回CFRunLoopRef類型的方法,你可以傳遞給Core Foundation的例程。因為兩個對象都引用相同的運行循環(huán),所以可以根據需要混合NSRunLoop對象和CFRunLoopRefopaque類型的調用。

配置運行循環(huán)

在輔助線程上運行運行循環(huán)之前,必須至少為其添加一個輸入源或計時器。如果運行循環(huán)沒有要監(jiān)視的任何源,則在您嘗試運行它時會立即退出。有關如何將源添加到運行循環(huán)的示例,請參閱配置運行循環(huán)源。

除了安裝源之外,您還可以安裝運行循環(huán)觀察器并使用它們來檢測運行循環(huán)的不同執(zhí)行階段。要安裝運行循環(huán)觀察器,您需要創(chuàng)建一個CFRunLoopObserverRef opaque類型并使用CFRunLoopAddObserver函數將其添加到運行循環(huán)中。必須使用Core Foundation創(chuàng)建運行循環(huán)觀察器,即使對于Cocoa應用程序也是如此。

清單3-1顯示了一個將運行循環(huán)觀察器附加到其運行循環(huán)的線程的主例程。該示例的目的是向您展示如何創(chuàng)建一個運行循環(huán)觀察器,因此代碼只是設置一個運行循環(huán)觀察器來監(jiān)視所有運行循環(huán)活動?;咎幚沓绦蚶蹋ㄎ达@示)僅在處理計時器請求時記錄運行循環(huán)活動。

清單3-1 創(chuàng)建一個運行循環(huán)觀察器

- (void)mainThread {
    //應用程序使用垃圾收集,因此不需要自動釋放池。
    NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
    
    //創(chuàng)建一個運行循環(huán)觀察器并將其附加到運行循環(huán)。
    CFRunLoopObserverContext context = {0, (__bridge void *)(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 {
        //運行10次運行循環(huán)讓計時器觸發(fā)。
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    } while (loopCount);
}

為長期存在的線程配置運行循環(huán)時,最好添加至少一個輸入源來接收消息。雖然您只能連接一個定時器進入運行循環(huán),但一旦定時器觸發(fā),它通常會失效,這會導致運行循環(huán)退出。附加重復計時器可以使運行循環(huán)運行更長的時間,但是會涉及定期觸發(fā)計時器以喚醒您的線程,這實際上是另一種形式的輪詢。相比之下,輸入源會等待事件發(fā)生,讓線程保持睡眠狀態(tài)。

啟動運行循環(huán)

只有應用程序中的輔助線程才需要啟動運行循環(huán)。運行循環(huán)必須至少有一個輸入源或計時器才能進行監(jiān)視。如果未連接,則運行循環(huán)立即退出。

有幾種方法可以啟動運行循環(huán),包括以下內容:

  • 無條件
  • 設定時限
  • 在特定模式下

無條件地進入運行循環(huán)是最簡單的選擇,但它也是最不可取的選擇。無條件地運行您的運行循環(huán)會將線程置于永久循環(huán)中,這使您幾乎無法控制運行循環(huán)本身。您可以添加和刪除輸入源和計時器,但停止運行循環(huán)的唯一方法是終止它。也無法在自定義模式下運行運行循環(huán)。

不要無條件地運行運行循環(huán),最好使用超時值運行運行循環(huán)。使用超時值時,運行循環(huán)將一直運行,直到事件到達或分配的時間到期。如果事件到達,則將該事件分派給處理程序進行處理,然后退出運行循環(huán)。然后,您的代碼可以重新啟動運行循環(huán)以處理下一個事件。如果指定的時間到期,您只需重新啟動運行循環(huán)或使用時間進行任何所需的任務處理。

除了超時值,您還可以使用特定模式運行運行循環(huán)。模式和超時值不是互斥的,可以在啟動運行循環(huán)時使用。模式限制將事件傳遞到運行循環(huán)的源類型,并在運行循環(huán)模式中進行了更詳細的描述。

清單3-2顯示了線程主入口例程的框架版本。此示例的關鍵部分顯示了運行循環(huán)的基本結構。實質上,您將輸入源和計時器添加到運行循環(huán)中,然后重復調用其中一個例程以啟動運行循環(huán)。每次運行循環(huán)例程返回時,您都會檢查是否出現了可能需要退出該線程的任何條件。該示例使用Core Foundation運行循環(huán)例程,以便它可以檢查返回結果并確定運行循環(huán)退出的原因。NSRunLoop如果使用Cocoa并且不需要檢查返回值,也可以使用類方法以類似的方式運行運行循環(huán)。(有關調用NSRunLoop類方法的運行循環(huán)的示例,請參閱清單3-14。)

清單3-2 運行一個運行循環(huán)

- (void)skeletonThreadMain {
    //如果不使用垃圾收集,在此處設置自動釋放池
    bool down = NO;
    
    //將輸入源或計時器添加到運行循環(huán)并執(zhí)行任何其他設置
    
    do {
        //啟動運行循環(huán),但在處理完每個源后返回。
        SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
        
        //如果源顯式停止了運行循環(huán)
        if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
            down = YES;
        
        //在這里檢查任何其他退出條件并設置
        //根據需要完成變量
    } while (!down);
}

退出運行循環(huán)

在處理事件之前,有兩種方法可以使運行循環(huán)退出:

  • 使用超時值運行配置運行循環(huán)。
  • 告訴運行循環(huán)停止。

如果您可以管理它,那么使用超時值肯定是首選。指定超時值可讓運行循環(huán)完成所有正常處理,包括在退出之前向運行循環(huán)觀察器發(fā)送通知。

使用該CFRunLoopStop函數顯式停止運行循環(huán)會產生類似于超時的結果。運行循環(huán)發(fā)出任何剩余的運行循環(huán)通知,然后退出。不同之處在于,您可以在無條件啟動的運行循環(huán)中使用此技術。

雖然刪除運行循環(huán)的輸入源和定時器也可能導致運行循環(huán)退出,但這不是停止運行循環(huán)的可靠方法。某些系統例程將輸入源添加到運行循環(huán)以處理所需的事件。因為您的代碼可能不知道這些輸入源,所以它將無法刪除它們,這將阻止運行循環(huán)退出。

配置運行循環(huán)源

以下部分顯示了如何在Cocoa和Core Foundation中設置不同類型的輸入源的示例。

定義自定義輸入源

創(chuàng)建自定義輸入源涉及定義以下內容:

  • 希望輸入源處理的信息。
  • 一個調度程序例程,讓感興趣的客戶端知道如何聯系這個輸入源。
  • 執(zhí)行任何客戶端發(fā)送的請求的處理程序例程。
  • 取消例程以使輸入源無效。

由于您創(chuàng)建了一個自定義輸入源來處理自定義信息,因此實際配置的設計非常靈活。調度程序,處理程序和取消例程是您自定義輸入源幾乎總是需要的關鍵例程。但是,大多數輸入源行為都發(fā)生在那些處理程序例程之外。例如,您可以定義將數據傳遞到輸入源以及將存在的輸入源傳遞給其他線程的機制。

圖3-2顯示自定義輸入源的示例配置。在此示例中,應用程序的主線程維護對輸入源的引用,該輸入源的自定義命令緩沖區(qū)以及安裝輸入源的運行循環(huán)。當主線程有一個要傳遞給工作線程的任務時,它會向命令緩沖區(qū)發(fā)布一個命令以及工作線程啟動任務所需的任何信息。(因為主線程和輸入源工作的線程都可以訪問命令緩沖區(qū),所以必須同步該訪問。)一旦發(fā)布命令,主線程就會發(fā)出信號輸入源并喚醒工作線程的運行循環(huán)。收到喚醒命令后,運行循環(huán)調用輸入源的處理程序,該處理程序處理命令緩沖區(qū)中的命令。

圖3-2 操作自定義輸入源

image

以下部分介紹了上圖中自定義輸入源的實現,并顯示了您需要實現的關鍵代碼。

定義輸入源

定義自定義輸入源需要使用Core Foundation框架來配置運行循環(huán)源并將其附加到運行循環(huán)。雖然基本處理程序是基于C的函數,但這并不妨礙您為這些函數編寫包裝器并使用Objective-C或C ++來實現代碼體。

圖3-2中引入的輸入源使用Objective-C對象來管理命令緩沖區(qū)并與運行循環(huán)協調。清單3-3顯示了該對象的定義。該RunLoopSource對象管理命令緩沖區(qū)并使用該緩沖區(qū)從其他線程接收消息。此列表還顯示了RunLoopContext對象的定義,它實際上只是用于將RunLoopSource對象和運行循環(huán)引用傳遞給應用程序主線程的容器對象。

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

@interface RunLoopSource : NSObject
{
    CFRunLoopSourceRef runLoopSource;
    NSMutableArray* commands;
}
 
- (id)init;
- (void)addToCurrentRunLoop;
- (void)invalidate;
 
// 處理方法
- (void)sourceFired;
 
// Client interface for registering commands to process
- (void)addCommand:(NSInteger)command withData:(id)data;
- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runloop;
 
@end
 
// 回調函數
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
void RunLoopSourcePerformRoutine (void *info);
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
 
// RunLoopContext是輸入源注冊的容器
@interface RunLoopContext : NSObject
{
    CFRunLoopRef        runLoop;
    RunLoopSource*        source;
}
@property (readonly) CFRunLoopRef runLoop;
@property (readonly) RunLoopSource* source;
 
- (id)initWithSource:(RunLoopSource*)src andLoop:(CFRunLoopRef)loop;
@end

盡管Objective-C代碼管理輸入源的自定義數據,但將輸入源附加到運行循環(huán)需要基于C的回調函數。當您將運行循環(huán)源實際附加到運行循環(huán)時,將調用這些函數中的第一個,如清單3-4所示。因為此輸入源只有一個客戶端(主線程),所以它使用調度程序函數發(fā)送消息以使用該線程上的應用程序委托注冊自身。當委托想要與輸入源通信時,它使用RunLoopContext對象中的信息來執(zhí)行此操作。

清單3-4 調度運行循環(huán)源

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];
}

最重要的回調例程之一是用于在輸入源發(fā)出信號時處理自定義數據的例程。清單3-5顯示了與該RunLoopSource對象關聯的執(zhí)行回調例程。此函數只是將請求轉發(fā)給sourceFired方法,然后處理命令緩沖區(qū)中存在的任何命令。

清單3-5 在輸入源中執(zhí)行工作

void RunLoopSourcePerformRoutine(void * info)
{
    RunLoopSource * obj =(RunLoopSource *)info;
    [obj sourceFired];
}

如果使用該CFRunLoopSourceInvalidate函數從運行循環(huán)中刪除輸入源,系統將調用輸入源的取消例程。您可以使用此例程通知客戶端您的輸入源不再有效,并且應刪除對它的任何引用。 清單3-6顯示了向RunLoopSource對象注冊的取消回調例程。此函數將另一個RunLoopContext對象發(fā)送到應用程序委托,但這次要求委托刪除對運行循環(huán)源的引用。

清單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:方法的代碼顯示在與輸入源的客戶端調度中。

在運行循環(huán)上安裝輸入源

清單3-7顯示了RunLoopSource類的init方法和addToCurrentRunLoop方法。該init方法創(chuàng)建CFRunLoopSourceRef必須實際附加到運行循環(huán)的opaque類型。它將RunLoopSource對象本身作為上下文信息傳遞,以便回調例程具有指向對象的指針。在工作線程調用該addToCurrentRunLoop方法之前不會安裝輸入源,此時將RunLoopSourceScheduleRoutine調用回調函數。一旦輸入源被添加到運行循環(huán)中,線程就可以運行其運行循環(huán)來等待它。

清單3-7 安裝運行循環(huán)源

- (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);
}

與輸入源的客戶協調

為了使輸入源有用,需要對其進行操作并從另一個線程發(fā)出信號。輸入源的重點是將其關聯的線程置于睡眠狀態(tài),直到有事情要做。這個事實需要讓應用程序中的其他線程知道輸入源并有辦法與之通信。

通知客戶端輸入源的一種方法是在輸入源首次安裝在其運行循環(huán)時發(fā)出注冊請求。可以根據需要向任意數量的客戶注冊輸入源,或者只需將其注冊到某個中央機構,然后將您的輸入源發(fā)送給感興趣的客戶。清單3-8顯示了應用程序委托定義的注冊方法,并在調用RunLoopSource對象的調度程序函數時調用。此方法接收RunLoopContext對象提供的RunLoopSource對象,并將其添加到其源列表中。此列表還顯示了從運行循環(huán)中刪除輸入源時用于取消注冊的例程。

清單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所示。

給輸入源發(fā)信號

在將數據移交給輸入源之后,客戶端必須向源發(fā)送信號并喚醒其運行循環(huán)。信號源使運行循環(huán)知道源已準備好進行處理。并且因為線程可能在信號發(fā)生時處于睡眠狀態(tài),所以應該總是明確地喚醒運行循環(huán)。如果不這樣做可能會導致處理輸入源的延遲。

清單3-9顯示了RunLoopSource對象的fireCommandsOnRunLoop方法。當客戶端準備好處理他們添加到緩沖區(qū)的命令時,客戶端會調用此方法。

清單3-9 喚醒運行循環(huán)

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

注意: 不應該通過發(fā)送自定義輸入源來嘗處理SIGHUP和其他類型的進程級信號。用于喚醒運行循環(huán)的Core Foundation函數不是信號安全的,不應在應用程序的信號處理程序例程中使用。有關信號處理程序例程的更多信息,請參見sigaction手冊頁

配置定時器源

要創(chuàng)建計時器源,要做的就是創(chuàng)建一個計時器對象并在運行循環(huán)上調度。在Cocoa中,您使用NSTimer類創(chuàng)建新的計時器對象,在Core Foundation中使用CFRunLoopTimerRef opaque類型。事實上,NSTimer類只是在Core Foundation上做一些簡單的擴展,它提供了一些便利功能,例如使用相同方法創(chuàng)建和調度計時器。

在Cocoa中,可以使用以下任一類方法一次創(chuàng)建和調度計時器:

  • scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
  • scheduledTimerWithTimeInterval:invocation:repeats:

這些方法創(chuàng)建計時器并在默認模式(NSDefaultRunLoopMode)中將其添加到當前線程的運行循環(huán)中。如果需要,還可以手動調度計時器,方法是創(chuàng)建NSTimer對象,然后使用addTimer:forMode:方法將其添加到運行循環(huán)中NSRunLoop。這兩種技術基本上都是一樣的,但是可以對計時器的配置進行不同程度的控制。例如,如果創(chuàng)建計時器并手動將其添加到運行循環(huán),則可以使用默認模式以外的模式執(zhí)行此操作。清單3-10顯示了如何使用這兩種技術創(chuàng)建計時器。第一個計時器的初始延遲為1秒,但之后每0.1秒定時觸發(fā)一次。第二個計時器在最初的0.2秒延遲后開始射擊,然后每0.2秒觸發(fā)一次。

清單3-10 使用NSTimer創(chuàng)建和調度計時器

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
                        repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
 
// 創(chuàng)建并調度第二個計時器
[NSTimer scheduledTimerWithTimeInterval:0.2
                        target:self
                        selector:@selector(myDoFireTimer2:)
                        userInfo:nil
                        repeats:YES];

清單3-11顯示了使用Core Foundation函數配置計時器所需的代碼。雖然此示例未在上下文結構中傳遞任何用戶定義的信息,但可以使用此結構傳遞計時器所需的任何自定義數據。有關此結構內容的更多信息,請參閱CFRunLoopTimer參考中的說明。

清單3-11 使用Core Foundation創(chuàng)建和調度計時器

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);

配置基于端口的輸入源

Cocoa和Core Foundation都提供了基于端口的對象,用于線程之間或進程之間的通信。以下部分介紹如何使用多種不同類型的端口設置端口通信。

配置NSMachPort對象

要與NSMachPort對象建立本地連接,請創(chuàng)建端口對象并將其添加到主線程的運行循環(huán)中。啟動輔助線程時,將同一對象傳遞給線程的入口點函數。輔助線程可以使用相同的對象將消息發(fā)送回主線程。

實現主線程代碼

清單3-12顯示了啟動輔助工作線程的主要線程代碼。因為Cocoa框架執(zhí)行許多配置端口和運行循環(huán)的中間步驟,所以該launchThread方法明顯短于其Core Foundation等效代碼(清單3-17); 然而,兩者的行為幾乎完全相同。一個區(qū)別是,該方法不是直接向工作線程發(fā)送本地端口的名稱,而是直接發(fā)送NSPort對象。

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

- (void)luanchThread {
    NSPort *myPort = [NSMachPort port];
    if (myPort) {
        //此類處理傳入的端口消息
        [myPort setDelegate:self];
        
        //在當前運行循環(huán)中將端口安裝為輸入源
        [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
        
        //分離線程,讓工作線程釋放端口
        [NSThread detachNewThreadSelector:@selector(luanchThreadWithPort:) toTarget:self withObject:myPort];
    }
}

為了在線程之間建立雙向通信通道,您可能希望讓工作線程在簽入消息中將其自己的本地端口發(fā)送到主線程。接收到簽入消息后,主線程就會知道在啟動第二個線程時一切順利,并且還為您提供了向該線程發(fā)送更多消息的方法。

清單3-13顯示了主線程的handlePortMessage:方法。當數據到達線程自己的本地端口時,將調用此方法。當簽入消息到達時,該方法直接從端口消息中檢索輔助線程的端口并保存以供以后使用。

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

#define kCheckinMessage 100
 
// 處理來自工作線程的響應
- (void)handlePortMessage:(NSPortMessage *)portMessage
{
    unsigned int message = [portMessage msgid];
    NSPort* distantPort = nil;
 
    if (message == kCheckinMessage)
    {
        // 獲取工作線程的通信端口
        distantPort = [portMessage sendPort];
 
        // 保留并保存工作端口以供以后使用
        [self storeDistantPort:distantPort];
    }
    else
    {
        // 處理其他消息
    }
}
實現輔助線程代碼

對于輔助工作線程,您必須配置線程并使用指定的端口將信息傳遞回主線程。

清單3-14顯示了設置工作線程的代碼。在為線程創(chuàng)建自動釋放池之后,該方法創(chuàng)建一個工作對象來驅動線程執(zhí)行。worker對象的sendCheckinMessage:方法(如清單3-15所示)為工作線程創(chuàng)建一個本地端口,并將一個簽入消息發(fā)送回主線程。

清單3-14 使用Mach端口啟動工作線程

+(void)LaunchThreadWithPort:(id)inData
{
    NSAutoreleasePool*  pool = [[NSAutoreleasePool alloc] init];
 
    // 設置與主線程的連接
    NSPort* distantPort = (NSPort*)inData;
 
    MyWorkerClass*  workerObj = [[self alloc] init];
    [workerObj sendCheckinMessage:distantPort];
    [distantPort release];
 
    // 讓RunLoop處理事情
    do
    {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                            beforeDate:[NSDate distantFuture]];
    }
    while (![workerObj shouldExit]);
 
    [workerObj release];
    [pool release];
}

使用NSMachPort時,本地和遠程線程可以使用相同的端口對象進行線程之間的單向通信。換句話說,由一個線程創(chuàng)建的本地端口對象成為另一個線程的遠程端口對象。

清單3-15顯示了輔助線程的簽入例程。此方法為將來的通信設置自己的本地端口,然后將簽入消息發(fā)送回主線程。該方法使用方法中接收的端口對象LaunchThreadWithPort:作為消息的目標。

清單3-15 使用Mach端口發(fā)送簽入消息

// 工作線程簽入方法
- (void)sendCheckinMessage:(NSPort*)outPort
{
    // 保留并保存遠程端口以備將來使用
    [self setRemotePort:outPort];
 
    // 創(chuàng)建并配置工作線程端口
    NSPort* myPort = [NSMachPort port];
    [myPort setDelegate:self];
    [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
 
    // 創(chuàng)建簽入消息
    NSPortMessage* messageObj = [[NSPortMessage alloc] initWithSendPort:outPort
                                         receivePort:myPort components:nil];
 
    if (messageObj)
    {
        // 完成消息配置并立即發(fā)送 immediately.
        [messageObj setMsgId:setMsgid:kCheckinMessage];
        [messageObj sendBeforeDate:[NSDate date]];
    }
}

配置NSMessagePort對象

要與NSMessagePort對象建立本地連接,不能簡單地在線程之間傳遞端口對象。必須按名稱獲取遠程消息端口。在Cocoa中實現這一點需要使用特定名稱注冊本地端口,然后將該名稱傳遞給遠程線程,以便它可以獲取適當的端口對象進行通信。清單3-16顯示了在您要使用消息端口的情況下的端口創(chuàng)建和注冊過程。

清單3-16 注冊消息端口

NSPort* localPort = [[NSMessagePort alloc] init];
 
// 配置對象并將其添加到當前運行循環(huán)中
[localPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:localPort forMode:NSDefaultRunLoopMode];
 
// 使用特定名稱注冊端口,名稱必須是唯一的
NSString* localPortName = [NSString stringWithFormat:@"MyPortName"];
[[NSMessagePortNameServer sharedInstance] registerPort:localPort
                     name:localPortName];

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

本節(jié)介紹如何使用Core Foundation在應用程序的主線程和工作線程之間建立雙向通信通道。

清單3-17顯示了應用程序主線程調用以啟動工作線程的代碼。代碼所做的第一件事是建立一個CFMessagePortRefopaque類型,用于偵聽來自工作線程的消息。工作線程需要端口的名稱來建立連接,因此在工作線程的入口點函數傳遞字符串值。端口名稱在當前用戶上下文中通常應該是唯一的; 否則,你可能會遇到沖突。

清單3-17 將Core Foundation消息端添附加到新線程

#define kThreadStackSize        (8 *4096)
 
OSStatus MySpawnThread()
{
    // 創(chuàng)建用于接收響應的本地端口
    CFStringRef myPortName;
    CFMessagePortRef myPort;
    CFRunLoopSourceRef rlSource;
    CFMessagePortContext context = {0, NULL, NULL, NULL, NULL};
    Boolean shouldFreeInfo;
 
    // 創(chuàng)建一個包含端口名稱的字符串
    myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.myapp.MainThread"));
 
    // 創(chuàng)建端口
    myPort = CFMessagePortCreateLocal(NULL,
                myPortName,
                &MainThreadResponseHandler,
                &context,
                &shouldFreeInfo);
 
    if (myPort != NULL)
    {
        // 端口已成功創(chuàng)建
        // 現在為它創(chuàng)建一個運行循環(huán)源
        rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
 
        if (rlSource)
        {
            // 將源添加到當前運行循環(huán)
            CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
 
            // 安裝完成后,可以釋放這些內容
            CFRelease(myPort);
            CFRelease(rlSource);
        }
    }
 
    // 創(chuàng)建線程并繼續(xù)處理
    MPTaskID        taskID;
    return(MPCreateTask(&ServerThreadEntryPoint,
                    (void*)myPortName,
                    kThreadStackSize,
                    NULL,
                    NULL,
                    NULL,
                    0,
                    &taskID));
}

安裝端口并啟動線程后,主線程可以在等待線程簽入時繼續(xù)其常規(guī)執(zhí)行。當簽入消息到達時,它將被分派到主線程的MainThreadResponseHandler函數,如清單3-18所示。。此函數提取工作線程的端口名稱,并為將來的通信創(chuàng)建管道。

清單3-18 接收簽入消息

#define kCheckinMessage 100
 
// 主線程端口消息處理程序
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);
 
        // 必須按名稱獲取遠程消息端口
        messagePort = CFMessagePortCreateRemote(NULL, (CFStringRef)threadPortName);
 
        if (messagePort)
        {
            // 保留并保存線程的通信端口以供將來參考
            AddPortToListOfActiveThreads(messagePort);
 
            // 由于前一個函數保留了端口,因此請釋放
            CFRelease(messagePort);
        }
 
        // Clean up.
        CFRelease(threadPortName);
        CFAllocatorDeallocate(NULL, buffer);
    }
    else
    {
        // 處理其他消息
    }
 
    return NULL;
}

配置主線程后,剩下的唯一事情就是新創(chuàng)建的工作線程創(chuàng)建自己的端口并簽入。清單3-19顯示了工作線程的入口點函數。該函數提取主線程的端口名稱,并使用它創(chuàng)建一個返回主線程的遠程連接。然后,該函數為自己創(chuàng)建一個本地端口,在線程的運行循環(huán)上安裝該端口,并向包含本地端口名稱的主線程發(fā)送簽入消息。

清單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();
}
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容