iOS RunLoop 編程手冊(cè) (譯)

這是一篇對(duì)Run Loop開發(fā)文檔《Threading Program Guide:Run Loops》的翻譯宪郊,來源于蘋果開發(fā)文檔墨闲。

Run loops 是和線程相關(guān)的基礎(chǔ)部分袱箱。一個(gè)run loop是一個(gè)用來調(diào)度工作和協(xié)調(diào)接受的事件的?循環(huán)。一個(gè)run loop的目的是有任務(wù)的時(shí)候保持線程忙碌向图,沒有任務(wù)的時(shí)候線程休眠荡短。

Runloop的管理并不是完全自動(dòng)的,你必須編寫線程代碼在合適的時(shí)間點(diǎn)啟動(dòng)runloop娜睛,并且響應(yīng)接收的事件髓霞。Cocoa和Core框架都提供了runloop對(duì)象供開發(fā)者配置和管理線程的runloop。然而你的應(yīng)用顯示不需要?jiǎng)?chuàng)建這些對(duì)象畦戒,app的框架在程序啟動(dòng)的過程中已經(jīng)自動(dòng)設(shè)置并且運(yùn)行了在主線程的runloop方库。

下面的章節(jié)提供了更多關(guān)于run loops和怎么在應(yīng)用中配置run loops的信息,更多的關(guān)于runloop 對(duì)象的信息查看NSRunLoop Class ReferenceCFRunLoop Reference

Run Loop解析

一個(gè)run loop和它的名字聽起來非常相似障斋,它是一個(gè)你的線程進(jìn)入的?循環(huán)纵潦,并且用戶使用它運(yùn)行事件處理程序來應(yīng)答事件。 你的代碼控制實(shí)現(xiàn)runloop的真正的?循環(huán)部分垃环。換句話說邀层,你的代碼提供了for或者while用來驅(qū)動(dòng)run loop。在你的?循環(huán)內(nèi)遂庄,你使用一個(gè)run loop對(duì)象來啟動(dòng)事件處理代碼----這些代碼能夠接收事件并且調(diào)用已安裝的事件處理程序寥院。

runloop接收的事件來自兩個(gè)不同類型的源,input source負(fù)責(zé)分發(fā)異步事件涛目,消息通常來自其他的線程或者一個(gè)不同的應(yīng)用程序秸谢。***timer source ***分發(fā)同步事件凛澎,這些事件發(fā)生在計(jì)劃的時(shí)間點(diǎn)或者重復(fù)的時(shí)間間隔。兩種類型的事件源都用一個(gè)應(yīng)用程序特定的程序處理方式來處理到來的事件估蹄。

圖標(biāo)3-1展示了runloop和各種各樣的事件源的概念結(jié)構(gòu)塑煎,輸入源異步地將事件發(fā)送給相應(yīng)的處理程序,并且導(dǎo)致 runUntilDate:方法被在特定線程相關(guān)的run loop調(diào)用使得runloop終止元媚,定時(shí)器源會(huì)給把事件傳遞給處理程序轧叽,但是不會(huì)導(dǎo)致runloop的終止。

Structure of a run loop and its sources.png

除了處理輸入源刊棕,run loops也會(huì)生成關(guān)于run loop的行為的通知炭晒,注冊(cè)run-loop 觀察者可以接收這些通知并且可以使用這些通知在線程上做額外的處理。你可以使用Core Foundation在線程上添加run loop觀察者甥角。

下面的節(jié)提供了更多關(guān)于run loop組成和run loop處理模式的信息网严,同樣描述了runloop在處理事件的不同時(shí)刻獲取到的通知。

1. Run Loop模式

一個(gè)run loop模式是一個(gè)將要被監(jiān)聽額輸入源和定時(shí)器的集合嗤无,?以及等待run loop通知的觀察者集合震束。你每次啟動(dòng)run loop,你顯示或者隱士的指定一個(gè)“模式”來運(yùn)行当犯,在run loop的運(yùn)行過程中垢村,只有和指定模式相關(guān)的源才會(huì)被監(jiān)聽和分發(fā)它們的事件(相似的,只有和指定模式關(guān)聯(lián)的觀察者才能獲得run loop運(yùn)行進(jìn)度的通知)嚎卫,和其他模式相關(guān)的輸入源會(huì)將任何接收到的事件保存起來嘉栓,直到后來以合適的模式?運(yùn)行run loop。

在你的代碼中拓诸,你可以通過名字識(shí)別運(yùn)行模式侵佃,Cocoa和Core Foundation都定義了一個(gè)默認(rèn)的模式和其他幾個(gè)通用的模式,可以通過字符串在代碼中指定奠支。你可以通過簡單為自定義的模式指定字符串名的方式實(shí)現(xiàn)自定義模式馋辈。雖然你在自定義模式下賦值的名字是任意的,但是這些模式的內(nèi)容卻不是隨意的倍谜,你必須確保為你創(chuàng)建的模式添加一個(gè)或多個(gè)輸入源迈螟、定時(shí)器或者run loop觀察者,這樣自定義的模式才會(huì)可用尔崔。

你使用模式可以在特定run loop運(yùn)行中過濾掉不想要的源的事件井联。大多數(shù)情況下,你會(huì)在“default”模式運(yùn)行代碼您旁。一個(gè)模態(tài)的面板烙常,然而可能運(yùn)行在“modal”模式下,因?yàn)樵谶@?種模式下,只有和模態(tài)面板相關(guān)的源才能夠把事件傳遞到線程上蚕脏。(這里是Mac開發(fā)的吧侦副,不理解) ,對(duì)于次要的線程,你可以使用自定義的模式阻止低優(yōu)先級(jí)的輸入源在時(shí)間要求比較嚴(yán)格的操作期間傳遞事件驼鞭。

注意:模式和事件的輸入源要區(qū)別對(duì)待秦驯,模式不是事件的類型。比如:你不能使用模式去單獨(dú)匹配鼠標(biāo)按下事件或者單獨(dú)匹配鍵盤事件挣棕。你可以使用模式來監(jiān)聽一組不同的端口(ports),暫時(shí)掛起定時(shí)器译隘。也可以改變正在被監(jiān)控的源和run loop的觀察者。

表3-1列舉了Cocoa和Core Foundationd的標(biāo)準(zhǔn)模式和使用的描述信息洛心,name這一欄列舉了在代碼中指定模式所使用的常量固耘。

預(yù)定義的模式.png

Default:大多數(shù)操作都使用的模式,大多數(shù)情況下你應(yīng)該在這個(gè)模式下開啟run loop词身,配置輸入源厅目。

Connection:Cocoa使用這個(gè)模式結(jié)合NSConnection對(duì)象來檢查依賴。你自己幾乎不會(huì)用到這種模式

Modal:Cocoa使用這個(gè)模式區(qū)分發(fā)送到模態(tài)面板的事件法严。
Event tracking:Cocoa用這個(gè)模式在鼠標(biāo)拖拽和其他類型用戶界面操作跟蹤過程中限制輸入的事件损敷。
Common modes:這是一個(gè)可以通常使用的模式的課配置的組合,和這個(gè)模式相關(guān)的輸入源同樣會(huì)和組里面的任意一個(gè)模式關(guān)聯(lián)深啤。對(duì)于Cocoa application拗馒,這個(gè)組默認(rèn)包含了default、modal溯街、event tracking模式诱桂,Core Foundation初始化時(shí)僅僅包含了default模式,你可以使用CFRunLoopAddCommonMode 添加自定義的模式苫幢。

2輸入源

輸入源異步的向你的線程分發(fā)事件访诱,事件的來源取決于輸入源的類型垫挨,通常是兩種類型的一種韩肝,基于端口的輸入源監(jiān)控你的應(yīng)用程序的Mach端口,自定義的輸入源監(jiān)控自定義事件源九榔。就你的run loop而言哀峻,它不會(huì)關(guān)心一個(gè)輸入源是自定義還是基于端口的。系統(tǒng)通常會(huì)實(shí)現(xiàn)兩種輸入源哲泊,你只管使用就可以了剩蟀。兩種輸入源的唯一區(qū)別是他們的信號(hào)是怎么獲得的∏型基于端口的源?由內(nèi)核發(fā)送信號(hào)育特,自定義的源必須手動(dòng)的在其他線程發(fā)信號(hào)。

當(dāng)你創(chuàng)建了一個(gè)輸入源,你給它指定一種或者多種運(yùn)行模式缰冤,模式?jīng)Q定了那些輸入源在任意給定的時(shí)刻會(huì)被監(jiān)視犬缨。大多數(shù)時(shí)間你在default模式下運(yùn)行,但是也可以指定自定義的模式棉浸。如果一個(gè)輸入源并不在當(dāng)前模式的監(jiān)視范圍怀薛,它產(chǎn)生的任意事件都會(huì)被保存直到run loop運(yùn)行在正確的模式。

2.1基于端口的源

Cocoa和Core Foundation為使用端口相關(guān)對(duì)象和功能創(chuàng)建基于端口的輸入源提供內(nèi)置支持迷郑,比如在Cocoa里面枝恋,你從來不需要直接創(chuàng)建輸入源,你只需要?jiǎng)?chuàng)建一個(gè)端口對(duì)象嗡害,調(diào)用NSPort的方法在run loop上添加端口焚碌,端口對(duì)象處理需要的輸入源的創(chuàng)建和配置。

在Core Foundation就漾,你必須手動(dòng)的創(chuàng)建端口和run loop輸入源呐能。在創(chuàng)建端口和輸入源的情況下,需要使用和對(duì)外不透透明的(開發(fā)文檔沒有描述)的類型(CFMachPortRef, CFMessagePortRef, or CFSocketRef)相關(guān)的函數(shù)創(chuàng)建合適的對(duì)象抑堡。

比如怎么創(chuàng)建一個(gè)和配置一個(gè)定制的基于端口的輸入源摆出,參考 7.7 配置基于端口的輸入源

2.2 自定義輸入源

創(chuàng)建一個(gè)定制的輸入源,必須使用在Core Foundation中不透明類CFRunLoopSourceRef相關(guān)的函數(shù)首妖,配置定制的輸入源用到幾個(gè)回調(diào)函數(shù)偎漫。Core Foundation會(huì)在不同的點(diǎn)調(diào)用這些函數(shù)配置源、處理到來的事件有缆、在源從run loop移除的時(shí)候銷毀源象踊。

除了定義自定輸入源在事件到來時(shí)的行為,你必須也定義事件的傳遞機(jī)制棚壁,輸入源的這部分運(yùn)行在一個(gè)單獨(dú)的線程上杯矩,并且負(fù)責(zé)提供輸入源的數(shù)據(jù)、在數(shù)據(jù)準(zhǔn)備處理的時(shí)候發(fā)信號(hào)給輸入源袖外。事件的傳遞機(jī)制取決于你史隆,但是不需要過于復(fù)雜。

有關(guān)如何創(chuàng)建自定義輸入源的示例曼验,請(qǐng)7.1 定義一個(gè)自定義的輸入源泌射。有關(guān)自定義輸入源的參考信息,請(qǐng)參閱“CFRunLoopSource”鬓照。

2.3 Cocoa執(zhí)行消息選擇器源--(Cocoa Perform Selector Sources)

除了基于端口的輸入源熔酷,Cocoa定義了一個(gè)自定義的輸入源允許你在任意線程上執(zhí)行selector的,就像基于端口的輸入源豺裆,在目標(biāo)線程上執(zhí)行selector的請(qǐng)求被序列化了拒秘,減少了許多在多個(gè)方法同時(shí)執(zhí)行在一個(gè)線程的情況下發(fā)生的同步問題。和基于端口不同的是,一個(gè)perform selector輸入源在執(zhí)行完selector后會(huì)自動(dòng)把自己從run loop移除躺酒。

在10.5 之前的OS X上咙轩,perform selector 輸入源主要給主線程發(fā)信息,在OS X10.5之后阴颖,可以給任意線程發(fā)消息活喊。

當(dāng)在線程上執(zhí)行一個(gè)selector的時(shí)候,該線程必須有一個(gè)活躍的run loop量愧,對(duì)于你創(chuàng)建的線程钾菊,這意味著一直等待到你的代碼顯示的開啟run loop。因?yàn)橹骶€程已經(jīng)開啟它的run loop了偎肃,所以程序一調(diào)用applicationDidFinishLaunching:就向該線程發(fā)出調(diào)用煞烫,run loop每進(jìn)行一次循環(huán)就會(huì)處理隊(duì)列化的perform selector的調(diào)用,而不是每次run loop循環(huán)處理隊(duì)列中的選一個(gè)處理累颂。

表3-2列舉了定義在NSObject可以在其他線程上執(zhí)行selecors的方法,因?yàn)檫@些方法定義在NSObject類里面滞详,你可以在任何你可以訪問到Objective-C對(duì)象的線程中使用,包括POSIX線程紊馏。這些方法實(shí)際上并不創(chuàng)建新的線程去執(zhí)行selector料饥。

Performing selectors on other threads.png
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:

執(zhí)行特定的selector在主線程的下一個(gè)run loop回路。這兩個(gè)方法給你提供了選項(xiàng)來阻斷當(dāng)前線程直到selector被執(zhí)行完畢朱监。

performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:

執(zhí)行特定的selector在任意線程上岸啡,這些線程通過NSThread對(duì)象表示。同樣提供了阻斷當(dāng)前線程直到selector被執(zhí)行赫编。

performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:

在當(dāng)前線程上下一個(gè)run loop回路中執(zhí)行selector巡蘸,并附加了延遲選項(xiàng)。因?yàn)樗却乱粋€(gè)run loop回路到來才執(zhí)行selector擂送,這些方法從當(dāng)前執(zhí)行的代碼中提供了一個(gè)自動(dòng)的微小延遲悦荒。多個(gè)排隊(duì)的selector會(huì)按照順序一個(gè)一個(gè)的執(zhí)行。

cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:

讓你取消一個(gè)通過performSelector:withObject:afterDelay: or performSelector:withObject:afterDelay:inModes: method方法發(fā)送到當(dāng)前線程的消息嘹吨。

每個(gè)方法更多詳細(xì)信息見NSObject Class Reference.

2.4 定時(shí)器源

定時(shí)器源在一個(gè)未來預(yù)先設(shè)置的時(shí)間同步?地傳遞事件給你的線程搬味,定時(shí)器也是一種線程通知自己做某些事情的實(shí)現(xiàn)方式。比如一個(gè)搜索框可以使用一個(gè)定時(shí)器去初始化一個(gè)自動(dòng)搜索躺苦,在用戶用戶連續(xù)輸入關(guān)鍵字的時(shí)間間隔大于某個(gè)數(shù)時(shí)觸發(fā)搜索身腻。延時(shí)的使用給了用戶一個(gè)在搜索開始之前盡可能多的去打印期望的關(guān)鍵字的機(jī)會(huì)产还。

雖然定時(shí)器產(chǎn)生了基于時(shí)間的通知匹厘,但是一個(gè)定時(shí)器并不是真正?實(shí)時(shí)機(jī)制。就像輸入源一樣脐区,定時(shí)器關(guān)聯(lián)了你的run loop里的特定的模式愈诚。如果一個(gè)timer并不是處于run loop當(dāng)前監(jiān)控的模式,定時(shí)器在你以定時(shí)器支持的模式運(yùn)行run loop之前就不會(huì)啟動(dòng)。
相似的炕柔,一個(gè)定時(shí)器如果在run loop執(zhí)行處理代碼的過程中開啟了酌泰,定時(shí)器會(huì)等到下一次run loop調(diào)用它的處理程序。如果run loop沒有運(yùn)行匕累,定時(shí)器永遠(yuǎn)不會(huì)啟動(dòng)陵刹。

你可以配置定時(shí)器一次或者重復(fù)的產(chǎn)生事件,一個(gè)重復(fù)的定時(shí)器自動(dòng)的在一個(gè)預(yù)定的啟動(dòng)(fire)時(shí)間開始重復(fù)調(diào)度自己欢嘿,并不是從真正的定時(shí)器fire的時(shí)間開始算衰琐。比如,一個(gè)定時(shí)器被設(shè)定在特定的時(shí)間點(diǎn)啟動(dòng)而且從那以后5秒鐘一次炼蹦。預(yù)定的?fire時(shí)間將永遠(yuǎn)會(huì)落?在于原來5s的時(shí)間間隔羡宙,如果真正的啟動(dòng)時(shí)間延遲。如果啟動(dòng)的時(shí)間延遲非常多以至于定時(shí)器錯(cuò)過了一次或多次預(yù)定的fire時(shí)間點(diǎn)掐隐,定時(shí)器只會(huì)在錯(cuò)過的時(shí)間片段內(nèi)啟動(dòng)一次狗热,在錯(cuò)過的時(shí)間段?fire后,定時(shí)器會(huì)重新設(shè)定下次預(yù)設(shè)的?fire時(shí)間虑省。

配置定時(shí)器更多參考 7.6 配置定時(shí)器, NSTimer Class Reference or CFRunLoopTimer Reference.

3. run loop 觀察者

與輸入源相反匿刮,當(dāng)一個(gè)合適的同步或者異步事件發(fā)生時(shí)輸入源會(huì)fire.而run loop觀察者在run loop本身自己執(zhí)行的過程中會(huì)在一個(gè)特殊的地方fire。你可以用run loop觀察者讓你的線程去處理一個(gè)給定的事件或者為run loop將要進(jìn)入睡眠準(zhǔn)備線程探颈。你同樣可以將run loop觀察者和run loop下面的事件關(guān)聯(lián)起來僻焚。

  • run loop的入口
  • run loop將要處理一個(gè)定時(shí)器
  • run loop 將要處理一個(gè)輸入源
  • run loop 將要進(jìn)入睡眠
  • run loop 已經(jīng)喚醒,但是還沒有處理喚醒run loop的事件
  • 退出run loop

你可以給app用 Core Foundation 添加run loop觀察者膝擂,創(chuàng)建一個(gè)run loop觀察者虑啤,你創(chuàng)建了一個(gè)CFRunLoopObserverRef的類型的對(duì)象,這個(gè)類型?持續(xù)跟蹤你自定義的回調(diào)和它關(guān)心的run loop活動(dòng)部分架馋。

和定時(shí)器相似狞山,run loop觀察者可以重復(fù)或者單次使用,一個(gè)單次使用的觀察者會(huì)在它fire后在run loop中移除叉寂,一個(gè)重復(fù)的觀察者依然依附在run loop上萍启。單次還是重復(fù)可以在創(chuàng)建的時(shí)候指定。

有關(guān)如何創(chuàng)建run loop 觀察者的示例屏鳍,請(qǐng)參閱6.2 配置run loop勘纯。有關(guān)參考信息,請(qǐng)參閱CFRunLoopObserver钓瞭。

4. run loop一些列的事件

每次你運(yùn)行run loop驳遵,你的線程的run loop會(huì)處理掛起的事件,并且會(huì)給它的觀察者發(fā)送通知山涡。處理的順序是非常特別的堤结,就是下面順序唆迁。

  • 1.通知觀察者run loop已經(jīng)進(jìn)入了循環(huán)。

  • 2.通知觀察者所有準(zhǔn)備就緒的定時(shí)器將要 fire

  • 3.通知觀察者所有非基于端口的輸入源將要 fire

  • 4.fire所有非基于端口的準(zhǔn)備fire的輸入源

  • 5.如果一個(gè)基于端口的輸入源準(zhǔn)備好了并且等待fire竞穷。立刻fire唐责。到第9部。

  • 6.通知觀察者線程將要睡眠

  • 7.將線程睡眠直到下面任意一個(gè)事件發(fā)生瘾带。

    • 一個(gè)事件到達(dá)了基于端口的源
    • 定時(shí)器fire
    • run loop設(shè)置了到期的超時(shí)事件
    • 顯示的指定run loop喚醒
  • 8.通知觀察者線程已經(jīng)喚醒鼠哥。

  • 9.處理掛起的事件。

    • 如果一個(gè)用戶定義的定時(shí)器fire看政。處理定時(shí)器事件并且重新啟動(dòng)run loop肴盏。到步驟2.
    • 如果一個(gè)輸入源fire,傳遞事件帽衙。
    • 如果run loop是被顯示的被喚醒菜皂,但超時(shí)事件還沒有到,重新啟動(dòng)run loop進(jìn)入步驟2.
  • 10.通知觀察者run loop已經(jīng)退出厉萝。

因?yàn)橛^察者從定時(shí)器和輸入源來的通知會(huì)在那些事件實(shí)際發(fā)生之前被傳遞過來恍飘,可能在事件發(fā)生的時(shí)刻和收到通知的時(shí)刻之間有間隔,如果在事件上時(shí)效性是非常嚴(yán)格的谴垫,你可以使用睡眠和從睡眠中醒來的通知來幫助你關(guān)聯(lián)事件之間的時(shí)間章母。

因?yàn)槎〞r(shí)器和其他的周期性的事件會(huì)在你運(yùn)行run loop的時(shí)候傳遞,所以要避免run loop對(duì)事件傳遞的打斷翩剪。一個(gè)經(jīng)典行為:每當(dāng)你通過一個(gè)循環(huán)不斷的從應(yīng)用程序請(qǐng)求事件來實(shí)現(xiàn)一個(gè)鼠標(biāo)的跟蹤程序的時(shí)候乳怎。因?yàn)槟愕拇a是直接捕獲的事件,而不是讓應(yīng)用程序正常的分發(fā)這些事件前弯,活躍的定時(shí)器將在你的鼠標(biāo)跟蹤程序退出并將控制權(quán)返回給應(yīng)用程序后失效蚪缀。

一個(gè)run loop可以用run loop對(duì)象顯示的喚醒,其他的事件同樣可以使run loop喚醒恕出。比如添加其他的非基于端口的輸入源可以喚醒run loop可以使得輸入源可以立即被處理询枚,而不是等到其他事件發(fā)生的時(shí)候。

5.什么時(shí)候會(huì)用一個(gè)run loop

唯一需要顯示的運(yùn)行一個(gè)run loop的場(chǎng)景是在應(yīng)用程序中創(chuàng)建了輔助線程浙巫。應(yīng)用程序主線程的run loop是基礎(chǔ)設(shè)施的關(guān)鍵部分金蜀。所以app的框架提供了運(yùn)行主線程run loop的代碼并且自動(dòng)開啟。iOS的UIAppliaction的run方法(或者OS X 的NSApplication)開啟一個(gè)應(yīng)用程序的main loop作為一些列程序啟動(dòng)流程的一部分的畴。如果你使用xcode模板工程創(chuàng)建應(yīng)用渊抄,你應(yīng)該從來不顯示的調(diào)用這些例程。

對(duì)于輔助線程丧裁,你需要決定一個(gè)run loop是不是必要的护桦,如果是,就配置并開啟它渣慕。你并不需要在任意情況下都開啟一個(gè)線程的run loop嘶炭。比如:如果你使用一個(gè)線程執(zhí)行某些長時(shí)間運(yùn)行并且是事先確定的任務(wù),你可以避免開啟run loop逊桦。run loops的目的是為了應(yīng)用在你想和線程有更多的交互的場(chǎng)合上的眨猎。比如:如果你想做下面的任何事情你就需要開啟run loop。

  • 使用端口或者自定義的輸入源和其他線程通信
  • 在線程上使用定時(shí)器
  • 在cocoa應(yīng)用中使用任意一個(gè)performSelector…方法
  • 使得線程不被殺死去做周期性任務(wù)

如果你選擇使用一個(gè)run loop强经,配置和創(chuàng)建是非常簡單的睡陪。和所有的線程編程一樣,你為在合適的場(chǎng)合下結(jié)束你的輔助線程指定計(jì)劃匿情。通常來說讓線程以結(jié)束的退出(exit)的方式要比強(qiáng)制讓線程終止的辦法好兰迫。怎么配置和退出run loop的描述信息在** 6. 使用run loop對(duì)象**.

6. 使用run loop對(duì)象

一個(gè)run loop對(duì)象提供了添加輸入源,定時(shí)器炬称,觀察者和運(yùn)行run loop的主要接口汁果,每一個(gè)線程都單獨(dú)有一個(gè)run loop對(duì)象和它關(guān)聯(lián)。在 Cocoa中這個(gè)對(duì)象是NSRunLoop類的一個(gè)實(shí)例玲躯,在低層次的應(yīng)用中据德,是一個(gè)CFRunLoopRef類型的指針。

6.1 獲取一個(gè)run loop對(duì)象

獲取當(dāng)前線程的run loop只需要用下面的一種方法:

  • 在Cocoa應(yīng)用跷车,使用NSRunLoop類的類方法currentRunLoop返回一個(gè)NSRunLoop對(duì)象
  • 使用CFRunLoopGetCurrent函數(shù)

雖然這兩個(gè)并不是可以自由的橋接類型棘利,但是你在必要的時(shí)候可以從一個(gè)NSRunLoop對(duì)象中獲取一個(gè)CFRunLoop類型。 通過NSRunLoop的getCFRunLoop方法獲得朽缴,然后傳遞給Core Foundation的代碼善玫。因?yàn)閮蓚€(gè)對(duì)象引用了相同的run loop,你可以根據(jù)需要隨意調(diào)用密强。

6.2 配置run loop

當(dāng)你在一個(gè)輔助線程上開啟run loop之前茅郎,必須給run loop添加至少一個(gè)輸入源或者一個(gè)定時(shí)器。如果一個(gè)run loop沒有任何源來監(jiān)控或渤,就會(huì)立刻退出只洒。參考7.配置run loop源

除了添加輸入源,你可以添加run loop觀察者劳坑,并且使用他們監(jiān)測(cè)run loop不同階段的操作毕谴,添加觀察者要?jiǎng)?chuàng)建一個(gè) CFRunLoopObserverRef 類型的對(duì)象,用CFRunLoopAddObserve函數(shù)添加到run loop上距芬。觀察者必須用Core Foundation創(chuàng)建涝开,即使在Cocoa應(yīng)用中。

3-1是一個(gè)綁定了觀察者的線程開啟它的run loop的代碼框仔。這個(gè)案例主要展示怎么創(chuàng)建run loop觀察者舀武,所以代碼只是簡單的創(chuàng)建了一個(gè)觀察者來監(jiān)控run loop的所有的活動(dòng)±胝叮基本的處理程序(沒有展示)簡單地在處理定時(shí)器請(qǐng)求的時(shí)候記錄了run loop的活動(dòng)银舱。

Listing 3-1 Creating a run loop observer

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

當(dāng)給長時(shí)間存在的線程配置run loop時(shí)瘪匿,最好添加一個(gè)輸入源來接收消息。即使你可以進(jìn)入一個(gè)只有一個(gè)定時(shí)器的run loop寻馏,一旦定時(shí)器fire棋弥,就無效了。會(huì)導(dǎo)致run loop退出诚欠。綁定一個(gè)重復(fù)的定時(shí)器可以使得run loop在一個(gè)長的時(shí)間段運(yùn)行顽染。但是需要定期的觸發(fā)定時(shí)器喚醒你的線程。這實(shí)際上是另一種形式的輪詢轰绵。相反粉寞,一個(gè)輸入源會(huì)等待事件的發(fā)生,在次之前會(huì)保持線程的休眠左腔。

6.3 開啟run loop

在應(yīng)用中開啟run loop僅僅對(duì)于輔助線程是必要的唧垦,run loop必須有至少一個(gè)輸入源或者定時(shí)器去監(jiān)控,如果一個(gè)都沒有就會(huì)立刻結(jié)束液样。

下面是幾種開啟run loop的方法:

  • 無條件的(Unconditionally)
  • 帶有時(shí)間限制設(shè)置的(With a set time limit)
  • 在特定的模式下(In a particular mode)

無條件的進(jìn)入run loop是最簡單的選項(xiàng)业崖,但是也是最不需要的。無條件的運(yùn)行run loop將線程放在一個(gè)永久的循環(huán)中蓄愁,對(duì)run loop本身的控制就非常少赘来。你可以添加或者移除輸入源或者定時(shí)器限煞,但是唯一使得run loop停止的方式是殺死它咪辱,而且沒有辦法在定制的模式下運(yùn)行run loop栗涂。

與其無條件啟動(dòng)run loop,不如給run loop設(shè)置一個(gè)超時(shí)時(shí)間運(yùn)行反而更好丹拯。當(dāng)你用一個(gè)超時(shí)時(shí)間值時(shí)站超,run loop會(huì)一直運(yùn)行直到事件的到來或者分配的時(shí)間用完。如果一個(gè)事件到來了乖酬,事件就會(huì)被分發(fā)給處理程序去處理死相,然后run loop退出。如果分配的時(shí)間過期了咬像,你可以簡單的重啟run loop或者花時(shí)間處理任何需要的事物算撮。

除了設(shè)置超時(shí)事件值外,你也可以給run loop以指定的模式運(yùn)行run loop县昂,模式和超時(shí)時(shí)間值并不互斥肮柜,可以同時(shí)添加。模式限制了傳遞給run loop事件的輸入源的類型倒彰。(詳細(xì)信息1. Run Loop模式.

3-2 是一個(gè)線程的主要代碼結(jié)構(gòu)审洞,關(guān)鍵部分是這個(gè)案例展示了run loop的基本結(jié)構(gòu),實(shí)際上你可以給run loop添加自己的輸入源和定時(shí)器然后重復(fù)的從多個(gè)程序例程中調(diào)用一個(gè)來啟動(dòng)run loop待讳。每次run loop例程程序返回芒澜,你檢查看看是否有任何可能導(dǎo)致線程結(jié)束的條件出現(xiàn)了仰剿。這個(gè)例子用了Core Foundation run loop程序,所以它可以檢查返回結(jié)果并且知道為什么run loop退出了痴晦,如果你用Cocoa南吮,同樣可以用 NSRunLoop的方法以一個(gè)相似的方式運(yùn)行run loop而且不用檢查返回值,在3-14.

Listing 3-2 Running a run loop

- (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.
}

遞歸的運(yùn)行一個(gè)run loop是有可能的阅酪,換句話說旨袒,你可以在輸入源或者定時(shí)器的處理程序中調(diào)用CFRunLoopRun,CFRunLoopRunInMode,或者其他的任意的NSRunLoop方法汁针。當(dāng)這樣做的時(shí)候术辐,你可以使用任何你想要的模式運(yùn)行嵌套的run loop,包括外層的run loop使用的模式施无。

6.4 退出 Run Loop

在使得一個(gè)run loop處理事件之前有兩種辦法結(jié)束run loop辉词。

  • 給run loop配置一個(gè)超時(shí)時(shí)間。
  • 告訴run loop停止

使用超時(shí)時(shí)間當(dāng)然是最好的猾骡,你可以管理它瑞躺。指定超時(shí)時(shí)間讓run loop結(jié)束它所有的在退出之前通常進(jìn)行的操作,包括給觀察者發(fā)通知兴想。

用CFRunLoopStop函數(shù)顯示的讓run loop和通過設(shè)置超時(shí)時(shí)間產(chǎn)生的效果是相似的幢哨。run loop會(huì)發(fā)出所有剩下run loop相關(guān)的通知然后退出,區(qū)別在于你可以在無條件啟動(dòng)的run loop上使用這個(gè)技術(shù)

雖然移除run loop的輸入源和定時(shí)器同樣會(huì)導(dǎo)致run loop退出嫂便,但是這并不是一個(gè)可靠的停止run loop的方式捞镰。有些系統(tǒng)程序給run loop增加輸入源處理必要的事件。因?yàn)槟愦a可能沒有意識(shí)到這些輸入源的存在毙替,它不能移除掉這些輸入源岸售,這會(huì)阻止run loop的退出。

7. 線程安全和Run Loop對(duì)象

線程安全的差異取決于你操作run loop所使用的API厂画,在Core Foundation的函數(shù)通常是線程安全的凸丸,而且可以被任何線程調(diào)用。然而如果你在執(zhí)行run loop配置的操作袱院,盡可能的從該run loop對(duì)應(yīng)的線程上操作依然是一個(gè)好的做法屎慢。

Cocoa的NSRunLoop類并不是像在Core Foundation中那樣線程安全的,如果你使用NSRunLoop來修改你的run loop忽洛,你應(yīng)該僅僅在run loop對(duì)應(yīng)的那個(gè)線程上操作抛人。添加一個(gè)輸入源或者定時(shí)器給非當(dāng)前線程的run loop會(huì)導(dǎo)致你的代碼崩潰或者產(chǎn)生不可預(yù)測(cè)的行為。

7. 配置 Run Loop 資源

下面章節(jié)的代碼是一些如何設(shè)置不同類型輸入源的案例(Cocoa和Foundation)

7.1 定義一個(gè)自定義的輸入源

創(chuàng)建一個(gè)自定義的輸入源包含如下定義選項(xiàng)

  • 輸入源希望處理的信息
  • 一個(gè)調(diào)度程序讓感興趣的客戶(client)知道怎么和你的輸入源取得聯(lián)系
  • 一個(gè)處理程序負(fù)責(zé)執(zhí)行客戶(client)發(fā)來的請(qǐng)求
  • 一個(gè)取消程序讓你輸入源無效

因?yàn)槟阕约簞?chuàng)建一個(gè)自定義的輸入源來處理自定義的信息脐瑰,實(shí)際的配置的設(shè)計(jì)是靈活的妖枚。調(diào)度程序和取消程序是關(guān)鍵程序,你的自定義輸入源幾乎總是需要的苍在,剩下的大部分輸入源行為發(fā)生在這些程序之外绝页。比如你可以定義傳遞數(shù)據(jù)給你的輸入源的機(jī)制和將輸入源的存在傳遞給其他線程荠商。

圖3-2是一個(gè)自定義輸入源配置的案例。這案例中程序的主線程維護(hù)對(duì)輸入源续誉、自定義輸入源的自定義命令緩沖區(qū)莱没、輸入源所在的run loop的引用。當(dāng)主線程有一個(gè)任務(wù)要交給工作線程的時(shí)候酷鸦,它會(huì)向命令緩沖區(qū)發(fā)送一個(gè)命令和工作線程需要的所有開始任務(wù)所需要的信息饰躲。(因?yàn)橹骶€程和工作線程都有訪問命令緩沖區(qū)的權(quán)限,訪問必須是同步的)一旦受到喚醒的命令臼隔,run loop調(diào)用輸入源的處理程序來處理在命令緩沖區(qū)的命令嘹裂。

3-2 操作一個(gè)自定義的輸入源.png

下面的章節(jié)解釋了上面圖標(biāo)自定義輸入源的實(shí)現(xiàn),和關(guān)鍵要實(shí)現(xiàn)的代碼

7.2 定義輸入源

自定義一個(gè)輸入源需要用Core Foundation的代碼來配置run loop資源摔握,并且將它和run loop依附在一起寄狼。雖然基礎(chǔ)的處理程序是C函數(shù),但是并不排除你需要用OC或者C++來封裝這些函數(shù)來實(shí)現(xiàn)你的代碼主體氨淌。

圖3-2中介紹的輸入源使用了OC對(duì)象來管理一個(gè)命令行緩沖區(qū)泊愧,協(xié)調(diào)run loop。3-3展示的是這個(gè)對(duì)象的定義盛正,RunLoopSource對(duì)象管理一個(gè)命令行緩沖區(qū)删咱,用緩沖區(qū)接收其他線程的消息。3-3同樣展示了RunLoopContext對(duì)象的定義豪筝,這是一個(gè)真正的用來傳遞一個(gè)RunLoopSource對(duì)象和run loop的引用到應(yīng)用程序主線程的容器對(duì)象痰滋。

Listing 3-3 The custom input source object definition

@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

雖然輸入源的自定義的數(shù)據(jù)是OC代碼管理的,但是將輸入源和run loop關(guān)聯(lián)在一起的代碼需要基于C的回調(diào)函數(shù)壤蚜,這些函數(shù)的第一個(gè)會(huì)在你真正將run loop源和run loop綁定的時(shí)候調(diào)用即寡,在3-4,因?yàn)檩斎朐粗挥幸粋€(gè)客戶(主線程)它使用調(diào)度程序中的函數(shù)發(fā)送一個(gè)信息來將自己在那個(gè)線程的應(yīng)用代理上注冊(cè)自己袜刷。當(dāng)代理想和輸入源取得聯(lián)系的時(shí)候聪富,就會(huì)使用RunLoopContext對(duì)象來實(shí)現(xiàn)。

Listing 3-4 Scheduling a run loop source

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)程序之一是用來在輸入源收到到信號(hào)時(shí)處理自定義數(shù)據(jù)的著蟹,3-5展示了執(zhí)行和RunLoopSource對(duì)象相關(guān)的回調(diào)代碼墩蔓。這個(gè)函數(shù)簡單的轉(zhuǎn)發(fā)了工作請(qǐng)求給sourceFired方法,這個(gè)方法會(huì)在以后處理命令緩沖區(qū)內(nèi)出現(xiàn)的任何命令萧豆。

Listing 3-5 Performing work in the input source

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

如果你使用CFRunLoopSourceInvalidate函數(shù)將輸入源移除奸披,系統(tǒng)會(huì)調(diào)用輸入源的取消代碼。你可以用這個(gè)代碼通知客戶們你的輸入源已經(jīng)不再有效了涮雷,他們應(yīng)該移除和它的所有的關(guān)聯(lián)阵面。3-6是RunLoopSource對(duì)象注冊(cè)的取消回調(diào)代碼。這個(gè)函數(shù)發(fā)送另一個(gè)RunLoopContext對(duì)象給應(yīng)用代理,但是這次是請(qǐng)求代理移除run loop源的關(guān)聯(lián)样刷。

Listing 3-6 Invalidating an input source

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: and removeSource:方法在Coordinating with Clients of the Input Source

7.3 在run loop上添加輸入源

3-7展示了RunLoopSource的init和addToCurrentRunLoop方法仑扑。init方法創(chuàng)建了必須依附到RunLoop上的CFRunLoopSourceRef非透明類型對(duì)象,它通過傳遞RunLoopSource對(duì)象本身作為上下文信息置鼻,所以回調(diào)程序會(huì)有指向該對(duì)象的指針镇饮。輸入源的安裝工作不會(huì)在工作線程調(diào)用addToCurrentRunLoop方法前進(jìn)行,addToCurrentRunLoop調(diào)用時(shí)RunLoopSourceScheduleRoutine的回調(diào)函數(shù)就會(huì)被調(diào)用箕母,一旦輸入源添加到run loop储藐,線程就可以運(yùn)行它的run loop來等待事件。

Listing 3-7 Installing the run loop source

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

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

為了輸入輸入源起作用嘶是,你應(yīng)該巧妙控制它并且在另一個(gè)線程給它發(fā)信號(hào)钙勃。輸入源的要點(diǎn)讓和它關(guān)聯(lián)的線程睡眠直到有事可做。所以讓其他的線程能夠獲得輸入源的信息和并且和輸入源進(jìn)行通信是現(xiàn)實(shí)的需求俊啼。

一個(gè)通知輸入源的客戶的方式是當(dāng)輸入源第一次安裝在run loop上的時(shí)候發(fā)送注冊(cè)請(qǐng)求肺缕∽笠剑可以為一個(gè)輸入源注冊(cè)多個(gè)客戶授帕,也可以簡單的注冊(cè)到一些中心機(jī)構(gòu),然后在把輸入源給感興趣的客戶浮梢。3-8展示了應(yīng)用程序代理的注冊(cè)并在RunLoopSource對(duì)象的調(diào)度函數(shù)被調(diào)用時(shí)執(zhí)行的注冊(cè)方法跛十,這個(gè)方法接收RunLoopSource對(duì)象提供的RunLoopContext對(duì)象,并且把它添加到源列表上秕硝,下面的代碼也包含了在從run loop移除的時(shí)候如何反注冊(cè)輸入源芥映。

Listing 3-8 Registering and removing an input source with the application delegate

- (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)函數(shù)調(diào)用的方法在上面的3-4和3-6

7 .5 給輸入源發(fā)信號(hào)

當(dāng)一個(gè)客戶把它的數(shù)據(jù)傳遞給輸入源后,必須給輸入源發(fā)信號(hào)喚醒它的run loop远豺,給輸入源發(fā)信號(hào)讓run loop知道輸入源已經(jīng)準(zhǔn)備好奈偏,等待處理。因?yàn)橐粋€(gè)信號(hào)發(fā)生的時(shí)候線程可能正在休眠躯护,你應(yīng)該總是顯示的喚醒run loop惊来。如果不這樣做可能會(huì)導(dǎo)致處理輸入源的數(shù)據(jù)上產(chǎn)生延遲。

3-9展示了RunLoopSource 對(duì)象的fireCommandsOnRunLoop方法棺滞,客戶在他們?yōu)檩斎朐醋龊锰幚砭彌_區(qū)數(shù)據(jù)的準(zhǔn)備時(shí)調(diào)用這個(gè)方法裁蚁。

Listing 3-9 Waking up the run loop

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

備注:你不應(yīng)該嘗試通過發(fā)送自定義輸入源來處理SIGHUP或其他類型的進(jìn)程級(jí)信號(hào),用于喚醒Run Loop的Core Foundation功能不是信號(hào)安全的继准,不應(yīng)該在應(yīng)用程序的信號(hào)處理程序中使用枉证。 有關(guān)信號(hào)處理程序例程的更多信息,請(qǐng)參閱sigaction手冊(cè)頁移必。

7.6 配置定時(shí)器

要?jiǎng)?chuàng)建定時(shí)器源室谚,你只需創(chuàng)建一個(gè)定時(shí)器對(duì)象并在Run Loop中調(diào)度。 在Cocoa中,您可以使用NSTimer類來創(chuàng)建新的定時(shí)器對(duì)象秒赤,而在Core Foundation中眨补,您可以使用CFRunLoopTimerRef類型。 在內(nèi)部倒脓,NSTimer類只是Core Foundation的擴(kuò)展撑螺,它提供了一些方便的功能,例如使用相同方法創(chuàng)建和計(jì)劃定時(shí)器的能力崎弃。

在Cocoa中甘晤,您可以使用以下任一類方法一次創(chuàng)建和調(diào)度定時(shí)器器:

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

這些方法創(chuàng)建定時(shí)器,并以默認(rèn)模式(NSDefaultRunLoopMode)將其添加到當(dāng)前線程的Run Loop中饲做。 如果您想通過創(chuàng)建NSTimer對(duì)象然后使用NSRunLoop的addTimer:forMode:方法將其添加到運(yùn)行循環(huán)中线婚,也可以手動(dòng)調(diào)度計(jì)時(shí)器。這兩種技術(shù)基本上都是一樣的盆均,但是給你不同級(jí)別的控制定時(shí)器配置塞弊。 例如,如果創(chuàng)建定時(shí)器并手動(dòng)將其添加到運(yùn)行循環(huán)中泪姨,則可以使用除默認(rèn)模式之外的模式來執(zhí)行此操作游沿。 清單3-10顯示了如何使用這兩種技術(shù)創(chuàng)建定時(shí)器。 第一個(gè)定時(shí)器的初始延遲為1秒肮砾,但隨后每0.1秒鐘定時(shí)fire诀黍。 第二個(gè)定時(shí)器在初始0.2秒延遲后開始首次fire,然后每0.2秒fire一次仗处。

Listing 3-10 Creating and scheduling timers using 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顯示了使用Core Foundation函數(shù)配置定時(shí)器所需的代碼眯勾。 雖然此示例不會(huì)在上下文結(jié)構(gòu)中傳遞任何用戶定義的信息,但您可以使用此結(jié)構(gòu)傳遞定時(shí)器所需的任何自定義數(shù)據(jù)婆誓。 有關(guān)此結(jié)構(gòu)的內(nèi)容的更多信息吃环,請(qǐng)參閱CFRunLoopTimer參考中的描述。

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

7.7 配置基于端口的輸入源

Cocoa和Core Foundation都提供基于端口的對(duì)象洋幻,用于線程之間或進(jìn)程之間的通信郁轻。 以下部分將介紹如何使用幾種不同類型的端口設(shè)置端口通信。

7.7.1 配置NSMachPort對(duì)象

要建立與NSMachPort對(duì)象的本地連接鞋屈,你將創(chuàng)建端口對(duì)象并將其添加到主線程的Run Loop中范咨。 啟動(dòng)輔助線程時(shí),將相同的對(duì)象傳遞給線程的入口點(diǎn)函數(shù)厂庇。 輔助線程可以使用相同的對(duì)象將消息發(fā)送回主線程渠啊。

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

清單3-12顯示了啟動(dòng)輔助工作線程的主線程代碼。 因?yàn)镃ocoa框架執(zhí)行了許多用于配置端口和run loop的介入步驟权旷,所以launchThread方法明顯短于其Core Foundation中等效的配置(清單3-17);然而替蛉,兩者的行為幾乎相同贯溅。 一個(gè)區(qū)別是,該方法不是將本地端口的名稱發(fā)送給工作線程躲查,而是直接發(fā)送NSPort對(duì)象它浅。

Listing 3-12 Main thread launch method

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

為了在線程之間建立一個(gè)雙向通信通道,你可能希望工作線程在登錄消息中將自己的本地端口發(fā)送到主線程镣煮。 接收簽入消息讓你的主線程知道在啟動(dòng)第二個(gè)線程時(shí)一切順利姐霍,并且還可以向你發(fā)送更多消息到該線程。清單3-13顯示了主線程的handlePortMessage:方法典唇。 當(dāng)數(shù)據(jù)到達(dá)線程自己的本地端口時(shí)調(diào)用此方法镊折。 當(dāng)一個(gè)簽到消息到達(dá)時(shí),該方法直接從端口消息中檢索次要線程的端口介衔,并保存以備以后使用恨胚。

Listing 3-13 Handling Mach port messages

#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.
    }
}

7.7.3實(shí)現(xiàn)次要線程代碼

對(duì)于輔助工作線程,你必須配置線程并使用指定的端口將信息傳回主線程炎咖。
清單3-14顯示了設(shè)置工作線程的代碼赃泡。 為線程創(chuàng)建自動(dòng)釋放池后,該方法將創(chuàng)建一個(gè)工作對(duì)象來驅(qū)動(dòng)線程執(zhí)行乘盼。 工作對(duì)象的sendCheckinMessage:方法(如清單3-15所示)為工作線程創(chuàng)建一個(gè)本地端口升熊,并將一個(gè)簽入消息發(fā)送回主線程。

Listing 3-14 Launching the worker thread using Mach ports

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

當(dāng)使用NSMachPort時(shí)蹦肴,本地和遠(yuǎn)程線程可以使用相同的端口對(duì)象進(jìn)行線程之間的單向通信僚碎。 換句話說猴娩,由一個(gè)線程創(chuàng)建的本地端口對(duì)象將成為另一個(gè)線程的遠(yuǎn)程端口對(duì)象阴幌。
清單3-15顯示了次要線程的簽入例程。 該方法設(shè)置自己的本地端口用于將來的通信卷中,然后發(fā)送一個(gè)檢入消息回主線程矛双。 該方法使用在LaunchThreadWithPort:方法中接收的端口對(duì)象作為消息的目標(biāo)。

Listing 3-15 Sending the check-in message using Mach ports

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

7.7.4 配置一個(gè)NSMessagePort對(duì)象

要建立與NSMessagePort對(duì)象的本地連接蟆豫,您不能簡單地在線程之間傳遞端口對(duì)象议忽。 遠(yuǎn)程消息端口必須以名稱獲取。 在Cocoa中可能需要使用特定的名稱注冊(cè)本地端口十减,然后將該名稱傳遞給遠(yuǎn)程線程栈幸,以便它可以獲取適當(dāng)?shù)亩丝趯?duì)象進(jìn)行通信。 清單3-16顯示了要使用消息端口的端口創(chuàng)建和注冊(cè)過程帮辟。

Listing 3-16 Registering a message port

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

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

本節(jié)介紹如何使用Core Foundation在應(yīng)用程序的主線程和工作線程之間設(shè)置雙向通信通道速址。清單3-17顯示了應(yīng)用程序主線程調(diào)用的代碼,以啟動(dòng)工作線程由驹。 代碼的第一件事是設(shè)置一個(gè)CFMessagePortRef opaque類型來監(jiān)聽來自工作線程的消息芍锚。 工作線程需要進(jìn)行連接的端口名稱,以便將字符串值傳遞給工作線程的入口點(diǎn)函數(shù)。 端口名稱通常在當(dāng)前用戶上下文中是唯一的; 否則并炮,您可能會(huì)遇到?jīng)_突默刚。

Listing 3-17 :將Core Foundation消息端口附加到新線程

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

在安裝端口并啟動(dòng)線程的情況下,主線程可以在等待線程檢入時(shí)繼續(xù)其正常執(zhí)行逃魄。當(dāng)檢入消息到達(dá)時(shí)荤西,它將被分派到主線程的MainThreadResponseHandler函數(shù),如清單3- 18伍俘。 此函數(shù)提取工作線程的端口名稱皂冰,并創(chuàng)建未來通信的管道。
Listing 3-18 Receiving the checkin message

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

在配置主線程之后养篓,唯一剩下的就是新創(chuàng)建的工作線程創(chuàng)建自己的端口并簽入秃流。清單3-19顯示了工作線程的入口點(diǎn)函數(shù)。 該函數(shù)提取主線程的端口名稱柳弄,并使用它來創(chuàng)建一個(gè)遠(yuǎn)程連接回主線程舶胀。 該函數(shù)然后為其自身創(chuàng)建本地端口,將端口安裝在線程的運(yùn)行循環(huán)上碧注,并向包含本地端口名稱的主線程發(fā)送檢入消息嚣伐。

Listing 3-19 Setting up the thread structures

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

一旦進(jìn)入其run loop,發(fā)送到線程端口的所有未來事件都將由ProcessClientRequest函數(shù)處理萍丐。 該功能的實(shí)現(xiàn)取決于線程工作的類型轩端,此處未顯示。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逝变,一起剝皮案震驚了整個(gè)濱河市基茵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壳影,老刑警劉巖拱层,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異宴咧,居然都是意外死亡根灯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門掺栅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烙肺,“玉大人,你說我怎么就攤上這事氧卧√殷希” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵假抄,是天一觀的道長怎栽。 經(jīng)常有香客問我丽猬,道長,這世上最難降的妖魔是什么熏瞄? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任脚祟,我火速辦了婚禮,結(jié)果婚禮上强饮,老公的妹妹穿的比我還像新娘由桌。我一直安慰自己,他們只是感情好邮丰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布行您。 她就那樣靜靜地躺著,像睡著了一般剪廉。 火紅的嫁衣襯著肌膚如雪娃循。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天斗蒋,我揣著相機(jī)與錄音捌斧,去河邊找鬼。 笑死泉沾,一個(gè)胖子當(dāng)著我的面吹牛捞蚂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跷究,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼姓迅,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了俊马?” 一聲冷哼從身側(cè)響起丁存,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎潭袱,沒想到半個(gè)月后柱嫌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屯换,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了与学。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彤悔。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖索守,靈堂內(nèi)的尸體忽然破棺而出晕窑,到底是詐尸還是另有隱情,我是刑警寧澤卵佛,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布杨赤,位于F島的核電站敞斋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疾牲。R本人自食惡果不足惜植捎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望阳柔。 院中可真熱鬧焰枢,春花似錦、人聲如沸舌剂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霍转。三九已至荐绝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間避消,已是汗流浹背很泊。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沾谓,地道東北人委造。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像均驶,于是被迫代替她去往敵國和親昏兆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • 由于文章長度限制妇穴,本文作為[譯]線程編程指南(一)后續(xù)部分爬虱。 Run Loops Run loop是與線程相關(guān)的基...
    巧巧的二表哥閱讀 1,178評(píng)論 0 5
  • Runloop 做了一年多的IOS開發(fā),對(duì)IOS和Objective-C深層次的了解還十分有限腾它,大多還停留在會(huì)用A...
    GitHubPorter閱讀 6,047評(píng)論 7 17
  • 什么是Run Loops RunLoops是與線程相關(guān)聯(lián)的基礎(chǔ)部分跑筝,一個(gè)Run Loop就是事件處理循環(huán),他是用來...
    傻傻小蘿卜閱讀 957評(píng)論 0 5
  • 做了一年多的IOS開發(fā)瞒滴,對(duì)IOS和Objective-C深層次的了解還十分有限曲梗,大多還停留在會(huì)用API的級(jí)別,這是...
    wangjianjun0730閱讀 577評(píng)論 0 2
  • Run Loops Run loops是與線程相關(guān)的基礎(chǔ)框架的一部分妓忍。一個(gè)run loop是一個(gè)循環(huán)虏两,在這個(gè)循環(huán)中...
    nemie閱讀 912評(píng)論 0 2