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

文檔地址

案例代碼下載

運行循環(huán)

運行循環(huán)是與線程相關(guān)的基礎(chǔ)架構(gòu)的一部分寂汇。一個運行循環(huán)是指用于安排工作,并協(xié)調(diào)接收傳入事件的事件處理循環(huán)定拟。運行循環(huán)的目的是在有任務(wù)時保持線程忙砌些,并在沒有任務(wù)時讓線程進入休眠狀態(tài)。

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

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

運行循環(huán)剖析

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

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

圖3-1顯示了運行循環(huán)和各種源的概念結(jié)構(gòu)。輸入源將異步事件傳遞給相應(yīng)的處理程序蔼紧,并導(dǎo)致該runUntilDate:方法(在線程的關(guān)聯(lián)NSRunLoop對象上調(diào)用)退出婆硬。計時器源將事件傳遞給其處理程序例程,但不會導(dǎo)致運行循環(huán)退出歉井。

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

image

除了處理輸入源之外柿祈,運行循環(huán)還會生成有關(guān)運行循環(huán)行為的通知。已注冊的運行循環(huán)觀察器可以接收這些通知并使用它們在線程上執(zhí)行其他處理哩至□锖浚可以使用Core Foundation在線程上安裝運行循環(huán)觀察器。

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

運行循環(huán)模式

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

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

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

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

表3-1列出了Cocoa和Core Foundation定義的標(biāo)準(zhǔn)模式以及何時使用該模式的說明蝎困。name列列出了用于在代碼中指定模式的實際常量录语。

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

模式 名稱 描述
默認 NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation) 默認模式是用于大多數(shù)操作。大多數(shù)情況下禾乘,應(yīng)該使用此模式啟動運行循環(huán)并配置輸入源澎埠。
連接 NSConnectionReplyMode (Cocoa) Cocoa將此模式與NSConnection對象結(jié)合使用以監(jiān)視回復(fù)。應(yīng)該很少需要使用此模式始藕。
模態(tài) NSModalPanelRunLoopMode (Cocoa) Cocoa使用此模式來識別用于模態(tài)面板的事件蒲稳。
事件跟蹤 NSEventTrackingRunLoopMode (Cocoa) Cocoa使用此模式在鼠標(biāo)拖動循環(huán)和其他種類的用戶界面跟蹤循環(huán)期間限制傳入事件。
常用模式 NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation) 這是一組可配置的常用模式伍派。將輸入源與此模式相關(guān)聯(lián)也會將其與組中的每個模式相關(guān)聯(lián)江耀。對于Cocoa應(yīng)用程序,此集合默認包括默認诉植,模態(tài)和事件跟蹤模式祥国。Core Foundation最初只包含默認模式”蹲伲可以使用該CFRunLoopAddCommonMode功能將自定義模式添加到集合中系宫。

輸入源

輸入源以異步方式向線程傳遞事件。事件的來源取決于輸入源的類型建车,通常是兩個類別中的一個扩借。基于端口的輸入源監(jiān)視應(yīng)用程序的Mach端口缤至。自定義輸入源監(jiān)視自定義事件源潮罪。就運行循環(huán)而言康谆,輸入源是基于端口還是自定義無關(guān)緊要。系統(tǒng)只是實現(xiàn)可以使用的兩種類型的輸入源嫉到。兩個來源之間的唯一區(qū)別是它們?nèi)绾伟l(fā)出信號沃暗。基于端口的源由內(nèi)核自動發(fā)出信號何恶,而自定義源必須從另一個線程手動發(fā)信號孽锥。

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

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

基于端口的源

Cocoa和Core Foundation提供內(nèi)置支持胎撇,使用與端口相關(guān)的對象和函數(shù)創(chuàng)建基于端口的輸入源介粘。例如,在Cocoa中创坞,根本不必直接創(chuàng)建輸入源碗短。只需創(chuàng)建一個端口對象,并使用NSPort方法將該端口添加到運行循環(huán)中题涨。port對象處理所需輸入源的創(chuàng)建和配置偎谁。

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

有關(guān)如何設(shè)置和配置基于端口的自定義源的示例铐望,請參閱配置基于端口的輸入源

自定義輸入源

要創(chuàng)建自定義輸入源茂附,在Core Foundation中必須使用與CFRunLoopSourceRef的opaque類型關(guān)聯(lián)的函數(shù)正蛙。可以使用多個回調(diào)函數(shù)配置自定義輸入源营曼。Core Foundation在不同的點調(diào)用這些函數(shù)來配置源乒验,處理傳入事件,并在從運行循環(huán)中刪除源時銷毀源蒂阱。

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

有關(guān)如何創(chuàng)建自定義輸入源的示例泪漂,請參閱定義自定義輸入源。有關(guān)自定義輸入源的參考信息新思,另請參閱CFRunLoopSource參考窖梁。

Cocoa執(zhí)行選擇器源

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

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

在另一個線程上執(zhí)行選擇器時扇救,目標(biāo)線程必須具有活動的運行循環(huán)刑枝。對于您創(chuàng)建的線程,這意味著要等到代碼顯式啟動運行循環(huán)迅腔。但是装畅,因為主線程啟動了自己的運行循環(huán),所以只要應(yīng)用程序調(diào)用applicationDidFinishLaunching:應(yīng)用程序委托的方法沧烈,就可以開始在該線程上發(fā)出調(diào)用 掠兄。運行循環(huán)每次通過循環(huán)處理所有排隊的執(zhí)行選擇器調(diào)用,而不是在每次循環(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)周期中闲礼,在應(yīng)用程序的主線程上執(zhí)行指定的選擇器牍汹。這些方法為您提供了阻止當(dāng)前線程直到執(zhí)行選擇器的選擇铐维。
performSelector:onThread:withObject:waitUntilDone:與performSelector:onThread:withObject:waitUntilDone:modes: 在您擁有NSThread對象的任何線程上執(zhí)行指定的選擇器。這些方法為您提供了阻止當(dāng)前線程直到執(zhí)行選擇器的選擇慎菲。
performSelector:withObject:afterDelay:與performSelector:withObject:afterDelay:inModes: 在下一個運行循環(huán)周期的可選延遲時間之后嫁蛇,在當(dāng)前線程上執(zhí)行指定的選擇器。因為它等待直到下一個運行循環(huán)周期來執(zhí)行選擇器露该,所以這些方法提供了來自當(dāng)前執(zhí)行代碼的自動延遲睬棚。多個排隊選擇器按排隊順序依次執(zhí)行。
cancelPreviousPerformRequestsWithTarget:與cancelPreviousPerformRequestsWithTarget:selector:object: 允許您取消使用performSelector:withObject:afterDelay:or performSelector:withObject:afterDelay:inModes:方法發(fā)送到當(dāng)前線程的消息解幼。

有關(guān)每種方法的詳細信息抑党,請參閱NSObject類參考

定時器源

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

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

您可以將計時器配置為僅生成一次或重復(fù)生成事件榕堰。重復(fù)計時器根據(jù)計劃的觸發(fā)時間自動重新計劃竖慧,而不是實際的觸發(fā)時間。例如逆屡,如果計劃在特定時間和之后每5秒計觸發(fā)一次計時器圾旨,則即使實際發(fā)射時間延遲,計劃發(fā)射時間也將始終落在原始的5秒時間間隔上魏蔗。如果發(fā)射時間延遲太多以至于錯過了一個或多個預(yù)定發(fā)射時間砍的,則計時器僅在錯過的時間段內(nèi)發(fā)射一次。在錯過的時間內(nèi)觸發(fā)后莺治,計時器被重新安排用于下一個預(yù)定的觸發(fā)時間廓鞠。

有關(guān)配置定時器源的更多信息帚稠,請參閱配置定時器源。有關(guān)參考信息床佳,請參閱NSTimer類參考CFRunLoopTimer參考滋早。

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

與在發(fā)生適當(dāng)?shù)漠惒交蛲绞录r觸發(fā)的源相反,運行循環(huán)觀察器在執(zhí)行運行循環(huán)期間在特殊位置觸發(fā)砌们。您可以使用運行循環(huán)觀察器來準(zhǔn)備線程以處理給定事件或在線程進入休眠狀態(tài)之前準(zhǔn)備線程杆麸。您可以將運行循環(huán)觀察器與運行循環(huán)中的以下事件相關(guān)聯(lián):

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

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

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

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

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

  1. 通知觀察者已經(jīng)輸入了運行循環(huán)光坝。
  2. 通知觀察者準(zhǔn)備好的計時器即將觸發(fā)尸诽。
  3. 通知觀察者任何非基于端口的輸入源即將觸發(fā)。
  4. 觸發(fā)任何準(zhǔn)備觸發(fā)的基于非端口的輸入源盯另。
  5. 如果基于端口的輸入源準(zhǔn)備就緒并等待觸發(fā)性含,請立即處理該事件。轉(zhuǎn)到第9步鸳惯。
  6. 通知觀察者線程即將睡眠商蕴。
  7. 將線程置于睡眠狀態(tài)叠萍,直到發(fā)生以下事件之一:
  • 基于端口的輸入源事件到達。
  • 計時器觸發(fā)绪商。
  • 為運行循環(huán)設(shè)置的超時值到期俭令。
  • 運行循環(huán)被明確喚醒。
  1. 通知觀察者線程剛剛醒來部宿。
  2. 處理待處理事件抄腔。
  • 如果觸發(fā)了用戶定義的計時器,則處理計時器事件并重新啟動循環(huán)理张。轉(zhuǎn)到第2步赫蛇。
  • 如果輸入源被觸發(fā),則傳遞事件雾叭。
  • 如果運行循環(huán)被明確喚醒但尚未超時悟耘,請重新啟動循環(huán)。轉(zhuǎn)到第2步织狐。
  1. 通知觀察者運行循環(huán)已退出暂幼。

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

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

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

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

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

對于輔助線程既峡,您需要確定是否需要運行循環(huán)羡榴,如果是,則自行配置并啟動它运敢。在有的情況下校仑,不需要啟動線程的運行循環(huán)。例如传惠,如果使用線程執(zhí)行某些長時間運行且預(yù)定義的任務(wù)迄沫,則可以避免啟動運行循環(huán)。運行循環(huán)適用于您希望與線程進行更多交互的情況卦方。例如蠕蚜,如果您計劃執(zhí)行以下任何操作匾竿,則需要啟動運行循環(huán):

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

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

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

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

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

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

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

配置運行循環(huán)

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

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

清單3-1顯示了一個將運行循環(huán)觀察器附加到其運行循環(huán)的線程的主例程樊拓。該示例的目的是向您展示如何創(chuàng)建一個運行循環(huán)觀察器,因此代碼只是設(shè)置一個運行循環(huán)觀察器來監(jiān)視所有運行循環(huán)活動塘慕〗钕模基本處理程序例程(未顯示)僅在處理計時器請求時記錄運行循環(huán)活動。

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

- (void)mainThread {
    //應(yīng)用程序使用垃圾收集图呢,因此不需要自動釋放池条篷。
    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ā),它通常會失效摊鸡,這會導(dǎo)致運行循環(huán)退出绽媒。附加重復(fù)計時器可以使運行循環(huán)運行更長的時間,但是會涉及定期觸發(fā)計時器以喚醒您的線程免猾,這實際上是另一種形式的輪詢是辕。相比之下,輸入源會等待事件發(fā)生猎提,讓線程保持睡眠狀態(tài)获三。

啟動運行循環(huán)

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

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

  • 無條件
  • 設(shè)定時限
  • 在特定模式下

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

不要無條件地運行運行循環(huán)耐亏,最好使用超時值運行運行循環(huán)。使用超時值時沪斟,運行循環(huán)將一直運行广辰,直到事件到達或分配的時間到期。如果事件到達主之,則將該事件分派給處理程序進行處理择吊,然后退出運行循環(huán)。然后槽奕,您的代碼可以重新啟動運行循環(huán)以處理下一個事件几睛。如果指定的時間到期,您只需重新啟動運行循環(huán)或使用時間進行任何所需的任務(wù)處理粤攒。

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

清單3-2顯示了線程主入口例程的框架版本吼蚁。此示例的關(guān)鍵部分顯示了運行循環(huán)的基本結(jié)構(gòu)。實質(zhì)上问欠,您將輸入源和計時器添加到運行循環(huán)中,然后重復(fù)調(diào)用其中一個例程以啟動運行循環(huán)粒蜈。每次運行循環(huán)例程返回時顺献,您都會檢查是否出現(xiàn)了可能需要退出該線程的任何條件。該示例使用Core Foundation運行循環(huán)例程枯怖,以便它可以檢查返回結(jié)果并確定運行循環(huán)退出的原因注整。NSRunLoop如果使用Cocoa并且不需要檢查返回值,也可以使用類方法以類似的方式運行運行循環(huán)。(有關(guān)調(diào)用NSRunLoop類方法的運行循環(huán)的示例肿轨,請參閱清單3-14寿冕。)

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

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

退出運行循環(huán)

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

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

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

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

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

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

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

定義自定義輸入源

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

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

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

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

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

image

以下部分介紹了上圖中自定義輸入源的實現(xiàn)丧失,并顯示了您需要實現(xiàn)的關(guān)鍵代碼。

定義輸入源

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

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

清單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
 
// 回調(diào)函數(shù)
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代碼管理輸入源的自定義數(shù)據(jù),但將輸入源附加到運行循環(huán)需要基于C的回調(diào)函數(shù)鲁沥。當(dāng)您將運行循環(huán)源實際附加到運行循環(huán)時,將調(diào)用這些函數(shù)中的第一個画恰,如清單3-4所示阐枣。因為此輸入源只有一個客戶端(主線程)蔼两,所以它使用調(diào)度程序函數(shù)發(fā)送消息以使用該線程上的應(yīng)用程序委托注冊自身妙啃。當(dāng)委托想要與輸入源通信時揖赴,它使用RunLoopContext對象中的信息來執(zhí)行此操作燥滑。

清單3-4 調(diào)度運行循環(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];
}

最重要的回調(diào)例程之一是用于在輸入源發(fā)出信號時處理自定義數(shù)據(jù)的例程肪跋。清單3-5顯示了與該RunLoopSource對象關(guān)聯(lián)的執(zhí)行回調(diào)例程州既。此函數(shù)只是將請求轉(zhuǎn)發(fā)給sourceFired方法易桃,然后處理命令緩沖區(qū)中存在的任何命令晤郑。

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

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

如果使用該CFRunLoopSourceInvalidate函數(shù)從運行循環(huán)中刪除輸入源,系統(tǒng)將調(diào)用輸入源的取消例程诫龙。您可以使用此例程通知客戶端您的輸入源不再有效签赃,并且應(yīng)刪除對它的任何引用锦聊。 清單3-6顯示了向RunLoopSource對象注冊的取消回調(diào)例程孔庭。此函數(shù)將另一個RunLoopContext對象發(fā)送到應(yīng)用程序委托怎抛,但這次要求委托刪除對運行循環(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];
}

注意: 應(yīng)用程序委托registerSource:和removeSource:方法的代碼顯示在與輸入源的客戶端調(diào)度中。

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

清單3-7顯示了RunLoopSource類的init方法和addToCurrentRunLoop方法蔓涧。該init方法創(chuàng)建CFRunLoopSourceRef必須實際附加到運行循環(huán)的opaque類型元暴。它將RunLoopSource對象本身作為上下文信息傳遞茉盏,以便回調(diào)例程具有指向?qū)ο蟮闹羔橉獭T诠ぷ骶€程調(diào)用該addToCurrentRunLoop方法之前不會安裝輸入源,此時將RunLoopSourceScheduleRoutine調(diào)用回調(diào)函數(shù)巍糯。一旦輸入源被添加到運行循環(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);
}

與輸入源的客戶協(xié)調(diào)

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

通知客戶端輸入源的一種方法是在輸入源首次安裝在其運行循環(huán)時發(fā)出注冊請求√忠拢可以根據(jù)需要向任意數(shù)量的客戶注冊輸入源固蚤,或者只需將其注冊到某個中央機構(gòu)夕玩,然后將您的輸入源發(fā)送給感興趣的客戶辆亏。清單3-8顯示了應(yīng)用程序委托定義的注冊方法扮叨,并在調(diào)用RunLoopSource對象的調(diào)度程序函數(shù)時調(diào)用碍沐。此方法接收RunLoopContext對象提供的RunLoopSource對象,并將其添加到其源列表中尘喝。此列表還顯示了從運行循環(huán)中刪除輸入源時用于取消注冊的例程朽褪。

清單3-8 使用應(yīng)用程序委托注冊和刪除輸入源

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

注意: 調(diào)用前面列表中的方法的回調(diào)函數(shù)如清單3-4和清單3-6所示。

給輸入源發(fā)信號

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

清單3-9顯示了RunLoopSource對象的fireCommandsOnRunLoop方法实夹。當(dāng)客戶端準(zhǔn)備好處理他們添加到緩沖區(qū)的命令時亮航,客戶端會調(diào)用此方法缴淋。

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

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

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

配置定時器源

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

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

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

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

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

NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
// 創(chuàng)建并調(diào)度第一個計時器
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)建并調(diào)度第二個計時器
[NSTimer scheduledTimerWithTimeInterval:0.2
                        target:self
                        selector:@selector(myDoFireTimer2:)
                        userInfo:nil
                        repeats:YES];

清單3-11顯示了使用Core Foundation函數(shù)配置計時器所需的代碼番刊。雖然此示例未在上下文結(jié)構(gòu)中傳遞任何用戶定義的信息芹务,但可以使用此結(jié)構(gòu)傳遞計時器所需的任何自定義數(shù)據(jù)。有關(guān)此結(jié)構(gòu)內(nèi)容的更多信息,請參閱CFRunLoopTimer參考中的說明磁滚。

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

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都提供了基于端口的對象维雇,用于線程之間或進程之間的通信吱型。以下部分介紹如何使用多種不同類型的端口設(shè)置端口通信。

配置NSMachPort對象

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

實現(xiàn)主線程代碼

清單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];
        
        //在當(dāng)前運行循環(huán)中將端口安裝為輸入源
        [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
        
        //分離線程,讓工作線程釋放端口
        [NSThread detachNewThreadSelector:@selector(luanchThreadWithPort:) toTarget:self withObject:myPort];
    }
}

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

清單3-13顯示了主線程的handlePortMessage:方法。當(dāng)數(shù)據(jù)到達線程自己的本地端口時脆贵,將調(diào)用此方法会烙。當(dāng)簽入消息到達時柏腻,該方法直接從端口消息中檢索輔助線程的端口并保存以供以后使用五嫂。

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

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

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

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

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

+(void)LaunchThreadWithPort:(id)inData
{
    NSAutoreleasePool*  pool = [[NSAutoreleasePool alloc] init];
 
    // 設(shè)置與主線程的連接
    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顯示了輔助線程的簽入例程痊乾。此方法為將來的通信設(shè)置自己的本地端口哪审,然后將簽入消息發(fā)送回主線程湿滓。該方法使用方法中接收的端口對象LaunchThreadWithPort:作為消息的目標(biāo)叽奥。

清單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中實現(xiàn)這一點需要使用特定名稱注冊本地端口较坛,然后將該名稱傳遞給遠程線程燎潮,以便它可以獲取適當(dāng)?shù)亩丝趯ο筮M行通信确封。清單3-16顯示了在您要使用消息端口的情況下的端口創(chuàng)建和注冊過程爪喘。

清單3-16 注冊消息端口

NSPort* localPort = [[NSMessagePort alloc] init];
 
// 配置對象并將其添加到當(dāng)前運行循環(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在應(yīng)用程序的主線程和工作線程之間建立雙向通信通道秉剑。

清單3-17顯示了應(yīng)用程序主線程調(diào)用以啟動工作線程的代碼。代碼所做的第一件事是建立一個CFMessagePortRefopaque類型略水,用于偵聽來自工作線程的消息渊涝。工作線程需要端口的名稱來建立連接跨释,因此在工作線程的入口點函數(shù)傳遞字符串值。端口名稱在當(dāng)前用戶上下文中通常應(yīng)該是唯一的; 否則阔涉,你可能會遇到?jīng)_突。

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

#define kThreadStackSize        (8 *4096)
 
OSStatus MySpawnThread()
{
    // 創(chuàng)建用于接收響應(yī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)建
        // 現(xiàn)在為它創(chuàng)建一個運行循環(huán)源
        rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
 
        if (rlSource)
        {
            // 將源添加到當(dāng)前運行循環(huán)
            CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
 
            // 安裝完成后,可以釋放這些內(nèi)容
            CFRelease(myPort);
            CFRelease(rlSource);
        }
    }
 
    // 創(chuàng)建線程并繼續(xù)處理
    MPTaskID        taskID;
    return(MPCreateTask(&ServerThreadEntryPoint,
                    (void*)myPortName,
                    kThreadStackSize,
                    NULL,
                    NULL,
                    NULL,
                    0,
                    &taskID));
}

安裝端口并啟動線程后函荣,主線程可以在等待線程簽入時繼續(xù)其常規(guī)執(zhí)行傻挂。當(dāng)簽入消息到達時兽肤,它將被分派到主線程的MainThreadResponseHandler函數(shù)资铡,如清單3-18所示笤休。。此函數(shù)提取工作線程的端口名稱闹啦,并為將來的通信創(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);
 
            // 由于前一個函數(shù)保留了端口,因此請釋放
            CFRelease(messagePort);
        }
 
        // Clean up.
        CFRelease(threadPortName);
        CFAllocatorDeallocate(NULL, buffer);
    }
    else
    {
        // 處理其他消息
    }
 
    return NULL;
}

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

清單3-19 設(shè)置線程結(jié)構(gòu)

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();
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末取劫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子亲雪,更是在濱河造成了極大的恐慌勇凭,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件义辕,死亡現(xiàn)場離奇詭異基显,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門居凶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弄兜,“玉大人盛垦,你說我怎么就攤上這事蔬充∮苟樱” “怎么了宾尚?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長灶似。 經(jīng)常有香客問我春感,道長甲献,這世上最難降的妖魔是什么球及? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任姑蓝,我火速辦了婚禮,結(jié)果婚禮上桃熄,老公的妹妹穿的比我還像新娘螟深。我一直安慰自己垢箕,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著癣诱,像睡著了一般欢策。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上懈费,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天衷畦,我揣著相機與錄音,去河邊找鬼冀痕。 笑死满哪,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赖舟。 我是一名探鬼主播幢泼,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼矿辽,長吁一口氣:“原來是場噩夢啊……” “哼前塔!你這毒婦竟也來了娜搂?” 一聲冷哼從身側(cè)響起既绕,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瓜挽,失蹤者是張志新(化名)和其女友劉穎祝拯,沒想到半個月后凄鼻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辆毡,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡情竹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年刁憋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惫皱。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡进苍,死狀恐怖学歧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砚哗,我是刑警寧澤庐船,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站密任,受9級特大地震影響口猜,放射性物質(zhì)發(fā)生泄漏辐真。R本人自食惡果不足惜钧敞,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诸迟。 院中可真熱鬧,春花似錦、人聲如沸阵苇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绅项。三九已至贮尖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趁怔,已是汗流浹背湿硝。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留润努,地道東北人关斜。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像铺浇,于是被迫代替她去往敵國和親痢畜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容