注: 本文對(duì)照
RunLoop
官方文檔翻譯, 有不對(duì)的地方還請(qǐng)幫忙指正, 謝謝!
目錄將就看下吧, 不解釋了...
- Runloop
- Run Loop Modes
- Input sources(輸入源)
- Port-Based Sources(基于端口的輸入源)
- Custom Input Sources(自定義輸入源)
- Cocoa Perform Selector Sources(執(zhí)行選擇器源)
- Timer Sources(定時(shí)器源)
- Run Loop Observers(觀察者)
- The Run Loop Sequence of Events(runloop 的事件隊(duì)列)
- When Would You Use a Run Loop?(什么時(shí)候使用 runloop)
- Using Run Loop Objects(使用 runloop 對(duì)象)
- Getting a Run Loop Object(獲取一個(gè) runloop 對(duì)象)
- Configuring the Run Loop
- Starting the Run Loop(啟動(dòng) runloop)
- Exiting the Run Loop(退出 runloop)
- Thread Safety and Run Loop Objects(線程安全和 runloop 對(duì)象)
- Configuring Run Loop Sources(配置 runloop 源)
- Defining a Custom Input Source(定義一個(gè)自定義輸入源)
- Defining the Input Source(定義輸入源)
- Installing the Input Source on the Run Loop(添加源到 runloop)
- Coordinating with Clients of the Input Source(協(xié)調(diào)輸入源的客戶端)
- Signaling the Input Source(向輸入源發(fā)送信號(hào))
- Configuring Timer Sources(配置 timer 源)
- Configuring a Port-Based Input Source(配置基于 port 的輸入源)
- Configuring an NSMachPort Object(NSMachPort 對(duì)象)
- Configuring an NSMessagePort Object(配置一個(gè) NSMessagePort)
- Configuring a Port-Based Input Source in Core Foundation(CoreFoundation 中創(chuàng)建 source1)
runloop 是與線程相關(guān)的基本基礎(chǔ)設(shè)施的一部分, 是一個(gè)事件處理循環(huán). 可以用它來(lái)調(diào)度工作并協(xié)調(diào)傳入事件的接收.
當(dāng)
runloop
有工作時(shí)會(huì)使當(dāng)前線程處于忙碌狀態(tài), 沒(méi)有則會(huì)讓線程休眠;每個(gè)線程都有對(duì)應(yīng)的
runloop
, 主線程的runloop
自動(dòng)開啟, 子線程需要手動(dòng)開啟;-
runloop
從兩種不同類型的源接收事件:- 輸入源, 傳遞異步事件, 通常來(lái)自另一個(gè)線程或另一個(gè)程序的消息, 如:
port
,custom
,performSelector:
等; - 定時(shí)器源
timer
, 提供同步事件, 發(fā)生在預(yù)定時(shí)間或重復(fù)間隔.
- 輸入源, 傳遞異步事件, 通常來(lái)自另一個(gè)線程或另一個(gè)程序的消息, 如:
輸入源(
Input sources
)將異步事件交付給相應(yīng)的處理程序, 會(huì)使runUntilDate:
方法(在線程的關(guān)聯(lián)NSRunLoop
對(duì)象上調(diào)用)無(wú)效, 即輪播圖的問(wèn)題;-
計(jì)時(shí)器源(
Timer sources
)將事件交付給它們的處理程序例程傲隶,但不會(huì)導(dǎo)致運(yùn)行循環(huán)退出.
observers
,runloop
狀態(tài)觀察者,runloop
會(huì)給run loop observers
發(fā)送runloop
狀態(tài)的通知, 使觀察者在線程上執(zhí)行額外的處理, 在CoreFoundation
中可以添加run loop observers
(CFRunLoopAddObserver()
).
Run Loop Modes
-
Run Loop Modes
是runloop
要監(jiān)視的輸入源和計(jì)時(shí)器源以及要通知的observers
的 集合. - 每次運(yùn)行
runloop
時(shí),都需要顯示/隱式指定運(yùn)行模式, 在runloop
循環(huán)過(guò)程中, 只監(jiān)視與該模式相關(guān)的源, 并允許交付它們的事件, 并且只有與該模式關(guān)聯(lián)的觀察者才會(huì)被通知運(yùn)行循環(huán)的進(jìn)度(狀態(tài))颠猴。
釋義:runloop
每次循環(huán)都有一個(gè)mode
, 只允許與該 mode 相關(guān)聯(lián)的源才能把事件給runloop
處理,runloop
的循環(huán)進(jìn)度也只有與該mode
相關(guān)聯(lián)的觀察者才會(huì)被通知. - 可以指定
runloop
運(yùn)行mode
, 但必須添加一個(gè)或多個(gè)輸入源,timer
或者觀察者到這個(gè)mode
中, 才能使該mode
起效. -
runloop
使用mode
過(guò)濾來(lái)自不需要的源的事件, 大多數(shù)情況下你希望以系統(tǒng)定義的default mode
下運(yùn)行, 但是模態(tài)面板下可能以 modalmode
運(yùn)行, 在這種mode
下,runloop
只處理modal mode
下的源的事件. 對(duì)于輔助線程, 可以使用自定義mode
防止低優(yōu)先級(jí)的源在時(shí)間關(guān)鍵的操作期間交付事件(當(dāng)前是A mode
,B mode
的事件放到自定義mode
中處理, 因?yàn)楫?dāng)前 A 的源的事件是優(yōu)先級(jí)最高的).
注意 : 模式的區(qū)分基于事件的源, 而不是事件的類型, 比如, 你不能使用 mode
來(lái)匹配鼠標(biāo)向下事件或鍵盤事件, 但可以使用 mode
監(jiān)聽一組不同的端口, 臨時(shí)掛起計(jì)時(shí)器, 或者改變?cè)从|發(fā)當(dāng)前被監(jiān)視的 runloop observers
.
釋義:
mode
只能用來(lái)匹配源, 不能匹配事件, 也就是mode
包含的是各種源, 源包含各種事件的這種對(duì)應(yīng)關(guān)系.
Predefined run loop modes :
Model | Name | Description |
---|---|---|
Default | NSDefaultRunloopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation) | 默認(rèn)模式用于大多數(shù)操作烂瘫。大多數(shù)情況下藕施,您應(yīng)該使用此模式啟動(dòng)運(yùn)行循環(huán)并配置輸入源. |
Connection | NSConnectionReplyMode (Cocoa) | Cocoa將此模式與NSConnection對(duì)象結(jié)合使用來(lái)監(jiān)視響應(yīng). |
Modal | NSModalPanelRunLoopMode (Cocoa) | Cocoa使用此模式來(lái)標(biāo)識(shí)用于模態(tài)面板的事件. |
Event tracking | NSEventTrackingRunLoopMode (Cocoa) | Cocoa使用此模式來(lái)限制鼠標(biāo)拖動(dòng)循環(huán)和其他用戶界面跟蹤循環(huán)期間的傳入事件. |
Common modes | NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation) | 這是一組可配置的常用模式。將輸入源與此模式關(guān)聯(lián)也將其與組中的每個(gè)模式關(guān)聯(lián)孝扛。對(duì)于Cocoa應(yīng)用程序舀锨,默認(rèn)情況下,這個(gè)集合包括默認(rèn)模式、模式和事件跟蹤模式呼盆。Core Foundation最初只包含默認(rèn)模式, 可以使用CFRunLoopAddCommonMode函數(shù)向集合添加自定義模式. |
Input sources(輸入源)
輸入源將事件 異步 地交付給線程, 事件的源取決于輸入源的類型, 通常分為兩類:
-
Port-Based Sources
(基于端口的源)監(jiān)視應(yīng)用程序的mach port
-
Custom Input Sources
(自定義輸入源)監(jiān)視事件的自定義源
系統(tǒng)實(shí)現(xiàn)了具有代表性的兩種類型的源, 兩個(gè)源之間的唯一區(qū)別就是它們?nèi)绾伟l(fā)出信號(hào)的, 基于端口的源是通過(guò)內(nèi)核自動(dòng)發(fā)出信號(hào), 而自定義的源則必須從另一個(gè)線程手動(dòng)發(fā)出信號(hào).
創(chuàng)建輸入源時(shí), 會(huì)把輸入源分配到runloop
的一個(gè)或多個(gè)mode
下, 任何時(shí)候添加的源都會(huì)被mode
監(jiān)視. 大多數(shù)情況下, 運(yùn)行默認(rèn)的default mode
, 也可以指定自定義源, 如果輸入源不是在當(dāng)前mode
下被監(jiān)視, 那么源生成的任何事件都將等待, 直到runloop
在對(duì)應(yīng)的mode
下才會(huì)被執(zhí)行.
Port-Based Sources(基于端口的輸入源)
Cocoa
和CoreFoundation
為使用Port-related
(端口相關(guān)) 對(duì)象和函數(shù)創(chuàng)建Port-Based
輸入源提供內(nèi)聯(lián)支持, 例如, 在Cocoa
中, 根本不需要直接創(chuàng)建輸入源, 只需要?jiǎng)?chuàng)建一個(gè)端口對(duì)象并且使用NSPort
類的方法將port
添加到runloop
,port
對(duì)象處理輸入源需要的創(chuàng)建和配置.
在CoreFoundation
中, 必須手動(dòng)創(chuàng)建端口及其運(yùn)行循環(huán)源.
在這兩種情況下, 都是通過(guò)使用與端口不透明類型(CFMachPortRef
年扩、CFMessagePortRef
或CFSocketRef
)相關(guān)的函數(shù)創(chuàng)建對(duì)象的.
Custom Input Sources(自定義輸入源)
在
CoreFoundation
中必須使用與不透明類型CFRunLoopSourceRef
相關(guān)的函數(shù)創(chuàng)建一個(gè)自定義輸入源, 使用幾個(gè)回調(diào)函數(shù)配置自定義輸入源,CoreFoundation
在配置源, 處理事件以及在從runloop
中銷毀源時(shí)會(huì)調(diào)用配置的回調(diào)函數(shù).
除了事件到達(dá)時(shí)的自定義源的行為外, 還需要定義事件交付機(jī)制, 源的這一部分運(yùn)行在一個(gè)單獨(dú)的線程上, 負(fù)責(zé)向輸入源提供數(shù)據(jù), 并且在數(shù)據(jù)準(zhǔn)備處理時(shí)發(fā)出信號(hào). 事件交付機(jī)制由創(chuàng)建者決定.
Cocoa Perform Selector Sources(執(zhí)行選擇器源)
除了基于端口的源之外,
Cocoa
還定義了一個(gè)可以在任何線程上perform selector
的自定義輸入源, 和基于端口的源類似,perform selector
的請(qǐng)求在目標(biāo)線程上序列化, 從而緩解了在一個(gè)線程上運(yùn)行多個(gè)方法時(shí)可能出現(xiàn)的許多同步問(wèn)題; 與基于端口的源不同的是perform selector
源會(huì)在執(zhí)行完成之后將自身從runloop
中移除.
當(dāng)在另一個(gè)線程
performming a selector
時(shí), 目標(biāo)線程必須有一個(gè)活躍的runloop
, 對(duì)于創(chuàng)建的線程(非主線程), 這意味著需要等到代碼顯式的開啟runloop
, 因?yàn)橹骶€程會(huì)啟動(dòng)自身的runloop
, 所以只要應(yīng)用程序調(diào)用了applicationDidFinishLaunching:
代理方法, 就可以對(duì)主線程發(fā)起調(diào)用,runloop
一次循環(huán)處理所有在等待的 Perform Selector, 而不是每次只調(diào)用一個(gè).
在
NSObject
上定義的方法, 用于在其他線程上執(zhí)行選擇器, 這些方法實(shí)際上并不創(chuàng)建執(zhí)行選擇器的新線程
- Performing selectors on other threads
Methods | Description |
---|---|
performSelectorOnMainThread:withObject:waitUntilDone: performSelectorOnMainThread:withObject:waitUntilDone:modes: | 在主線程的下次 runloop 循環(huán)中執(zhí)行特定的 selector, 提供在執(zhí)行 selector 之前阻塞當(dāng)前線程的選項(xiàng). |
performSelector:onThread:withObject:waitUntilDone: performSelector:onThread:withObject:waitUntilDone:modes: | 在具有NSThread對(duì)象的任何線程上執(zhí)行指定的選擇器, 提供在執(zhí)行 selector 之前阻塞當(dāng)前線程的選項(xiàng). |
performSelector:withObject:afterDelay: performSelector:withObject:afterDelay:inModes: | 在當(dāng)前線程的下次循環(huán)中延遲一定時(shí)間后執(zhí)行特定的 selector, 多個(gè)隊(duì)列選擇器按它們排隊(duì)的順序依次執(zhí)行 |
cancelPreviousPerformRequestsWithTarget: cancelPreviousPerformRequestsWithTarget:selector:object: | 取消使用延遲(afterDelay:)發(fā)送到當(dāng)前線程的消息。 |
Timer Sources(定時(shí)器源)
定時(shí)器源在將來(lái)的某個(gè)預(yù)設(shè)時(shí)間將事件同步地交付給線程, 定時(shí)器是線程通知自身做某事的一種方式.
盡管定時(shí)器生成基于時(shí)間的通知访圃,但它并不是一種實(shí)時(shí)機(jī)制厨幻。與輸入源一樣,計(jì)時(shí)器與
runloop mode
相關(guān)聯(lián)腿时。如果計(jì)時(shí)器沒(méi)有處于runloop
當(dāng)前監(jiān)視的模式况脆,則在以 計(jì)時(shí)器支持的模式之一 運(yùn)行runloop
之前,它不會(huì)觸發(fā)圈匆。類似地漠另,如果計(jì)時(shí)器在runloop
執(zhí)行處理程序例程時(shí)觸發(fā),則計(jì)時(shí)器將等到下一次執(zhí)行運(yùn)行循環(huán)時(shí)調(diào)用其處理程序例程跃赚。如果runloop
根本不運(yùn)行笆搓,那么定時(shí)器就不會(huì)觸發(fā)。
定時(shí)器可以生成一次或重復(fù)事件, 重復(fù)事件根據(jù)預(yù)定觸發(fā)時(shí)間(不是實(shí)際觸發(fā)時(shí)間)重新調(diào)度自身. 舉例(輪播圖與scrollView
的問(wèn)題).
Run Loop Observers(觀察者)
與在適當(dāng)?shù)漠惒交蛲绞录l(fā)生時(shí)的觸發(fā)源相反纬傲,運(yùn)行循環(huán)觀察者在
runloop
本身執(zhí)行期間在特定位置觸發(fā)源. 可以使用run loop
observer
來(lái)準(zhǔn)備線程來(lái)處理給定的事件满败,或者在線程休眠之前準(zhǔn)備線程.
runloop
中主要有以下事件可以觸發(fā)run loop observer
:
- The entrance to the run loop. --
runloop
進(jìn)入循環(huán)時(shí) - When the run loop is about to process a timer. --
runloop
將要處理計(jì)時(shí)器時(shí) - When the run loop is about to process an input source. --
runloop
將要處理輸入源時(shí) - When the run loop is about to go to sleep. --
runloop
將要進(jìn)入睡眠時(shí) - When the run loop has woken up, but before it has processed the event that woke it up. --
runloop
被事件喚醒, 還未處理該事件時(shí) - The exit from the run loop. --
runloop
退出循環(huán)時(shí)
對(duì)應(yīng)可選類型:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
可以使用
CoreFoundation
向應(yīng)用程序添加運(yùn)行循環(huán)觀察者, 要?jiǎng)?chuàng)建一個(gè)runloop observer
, 需要?jiǎng)?chuàng)建一個(gè)CFRunLoopObserverRef
實(shí)例, 該類型跟蹤自定義回調(diào)函數(shù)和想要監(jiān)聽的活動(dòng)(runloop
的狀態(tài)).
runloop observers
也可以設(shè)置一次或多次監(jiān)聽, 一次性的在執(zhí)行之后會(huì)從runloop
中刪除自身, 重復(fù)的observer
會(huì)保持附加.
The Run Loop Sequence of Events(runloop 的事件隊(duì)列)
每次運(yùn)行時(shí),線程的運(yùn)行循環(huán)處理等待的事件并為任何附加的觀察者生成通知, 具體順序如下:
- Notify observers that the run loop has been entered. -- 通知觀察者
runloop
已經(jīng)進(jìn)入循環(huán) - Notify observers that any ready timers are about to fire. -- 通知觀察者
timers
將要觸發(fā)事件 - Notify observers that any input sources that are not port based are about to fire. -- 通知觀察者 不是基于端口的輸入源 將要觸發(fā)
- Fire any non-port-based input sources that are ready to fire. -- 通知觀察者 不是基于端口的輸入源 觸發(fā)了
- If a port-based input source is ready and waiting to fire, process the event immediately. Go to step 9. -- 如果一個(gè)基于端口的輸入源準(zhǔn)備且等待觸發(fā), 立即處理該事件 ==> step 9.
- Notify observers that the thread is about to sleep. -- 通知觀察者 線程將要休眠
- Put the thread to sleep until one of the following events occurs: -- 將線程線程置為休眠直到下面情況出現(xiàn)
- An event arrives for a port-based input source. -- 基于端口的輸入源的事件到達(dá)
- A timer fires. --
timer
事件觸發(fā) - The timeout value set for the run loop expires. -- 超過(guò)了為運(yùn)行循環(huán)設(shè)置的超時(shí)值
- The run loop is explicitly woken up. -- 被顯式地喚醒
- Notify observers that the thread just woke up. -- 通知觀察者 線程剛剛被喚醒
- Process the pending event. -- 處理等待的事件
- If a user-defined timer fired, process the timer event and restart the loop. Go to step 2. -- 如果一個(gè)用戶定義的
timer
觸發(fā), 處理timer
事件, 重新啟動(dòng)循環(huán) ==> step 2. - If an input source fired, deliver the event. -- 如果輸入源觸發(fā), 交付事件
- If the run loop was explicitly woken up but has not yet timed out, restart the loop. Go to step 2. -- 如果
runloop
被顯示的喚醒, 但還未超時(shí), 重新啟動(dòng)循環(huán) ==> step 2.
- If a user-defined timer fired, process the timer event and restart the loop. Go to step 2. -- 如果一個(gè)用戶定義的
- Notify observers that the run loop has exited. -- 通知觀察者
runloop
已經(jīng)退出.
對(duì)應(yīng)流程如下圖:
由于計(jì)時(shí)器和輸入源的觀察者通知是在這些事件實(shí)際發(fā)生之前交付的叹括,因此通知的時(shí)間和實(shí)際事件的時(shí)間之間可能存在差距算墨。如果這些事件之間的時(shí)間非常關(guān)鍵,則可以使用
sleep
和sleep-from-sleep
通知來(lái)幫助您關(guān)聯(lián)實(shí)際事件之間的時(shí)間汁雷。
可以使用runloop
對(duì)象顯示地喚醒runloop
,其他事件也可能使runloop
被喚醒, 例如, 添加另一個(gè)非基于端口的輸入源將喚醒運(yùn)行循環(huán)净嘀,以便可以立即處理輸入源,而不是等到其他事件發(fā)生. (timer
)
When Would You Use a Run Loop?(什么時(shí)候使用 runloop)
唯一需要顯示地 run the
runloop
是為應(yīng)用程序創(chuàng)建輔助線程(子線程)的時(shí)候, 應(yīng)用主線程的runloop
是至關(guān)重要的基礎(chǔ)設(shè)施, 因此侠讯,應(yīng)用程序框架提供了運(yùn)行主應(yīng)用程序循環(huán)并自動(dòng)啟動(dòng)該循環(huán)的代碼挖藏。iOS 中UIApplication
(OS X 中的NSApplication
) 的run
方法啟動(dòng)應(yīng)用程序的主循環(huán)作為正常啟動(dòng)序列中的一部分.
子線程中,你需要決定是否需要一個(gè)runloop
, 如果是, 你自己配置并啟動(dòng)它.
在所有情況下都不需要啟動(dòng)線程的運(yùn)行循環(huán). 例如, 如果使用一個(gè)線程
perform
一些長(zhǎng)時(shí)間運(yùn)行且預(yù)先確定的 task, 可以避免啟動(dòng)runloop
. 適用于需要與線程進(jìn)行更多交互的情況, 例如, 如果想要執(zhí)行以下任何操作, 都需要開啟一個(gè)runloop
:
- Use ports or custom input sources to communicate with other threads.
-- 使用基于接口或者自定義輸入源來(lái)與其他線程進(jìn)行通訊. - Use timers on the thread.
-- 在線程上使用timers
- Use any of the performSelector… methods in a Cocoa application.
-- 在Cocoa
應(yīng)用中使用任何performSelector...
方法 - Keep the thread around to perform periodic tasks.
-- 保留線程以執(zhí)行周期性任務(wù)
如果決定使用
runloop
, 那么配置是非常簡(jiǎn)單的. 但是, 和所有的線程編程一樣, 你應(yīng)該有計(jì)劃的使用, 以便在適當(dāng)?shù)那闆r下退出子線程.
通過(guò)讓線程退出干凈地結(jié)束runloop
總比強(qiáng)制終止的好.
Using Run Loop Objects(使用 runloop 對(duì)象)
runloop
對(duì)象提供了主入接口, 用于向runloop
中添加input sources
,timers
, andrun-loop observers
并且運(yùn)行它.
每個(gè)線程都有一個(gè)runloop
對(duì)象與之對(duì)應(yīng), 在Cocoa
中, 這個(gè)對(duì)象是NSRunLoop
類的實(shí)例. 在底層應(yīng)用程序中, 是一個(gè)指向CFRunLoopRef
類型的指針.
Getting a Run Loop Object(獲取一個(gè) runloop 對(duì)象)
獲取給當(dāng)前線程的
runloop
:
- In a Cocoa application, use the currentRunLoop class method of NSRunLoop to retrieve an NSRunLoop object. -- 在
Cocoa
應(yīng)用中, 使用NSRunLoop
中的currentRunLoop
類方法獲取. - Use the CFRunLoopGetCurrent function. -- 使用
CFRunLoopGetCurrent
函數(shù).
可以從
NSRunLoop
對(duì)象獲得CFRunLoopRef
不透明類型.NSRunLoop
類定義了一個(gè)getCFRunLoop
方法,該方法返回一個(gè)可以傳遞給CoreFoundation
例程的CFRunLoopRef
類型. 因?yàn)檫@兩個(gè)對(duì)象引用同一個(gè)runloop
厢漩,所以可以根據(jù)需要混合調(diào)用NSRunLoop
對(duì)象和CFRunLoopRef
不透明類型.
Configuring the Run Loop
在 run the
runloop
之前, 必須給runloop
添加至少一個(gè)輸入源或者timer
, 如果runloop
在沒(méi)有任何源需要監(jiān)視, 在你嘗試 run 的時(shí)候runloop
會(huì)立即退出.
除了添加源之外, 還可以添加runloop observer
, 用他們來(lái)監(jiān)視runloop
的不同執(zhí)行階段. 想要添加runloop observer
, 需要?jiǎng)?chuàng)建一個(gè)CFRunLoopObserverRef
類型, 使用CFRunLoopAddObserver
函數(shù)來(lái)把它添加到runloop
中, 必須使用CoreFoundation
創(chuàng)建run loop observer
, 即使對(duì)于Cocoa
應(yīng)用程序也是如此膜眠。
官方示例(主線程):
- (void)threadMain {
// The application uses garbage collection,
// so no autorelease pool is needed.
// 獲取當(dāng)前線程
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create a run loop observer and attach it to the run loop.
// 創(chuàng)建一個(gè) runloop observer 附加到 runloop 中.
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.
// 創(chuàng)建并執(zhí)行定時(shí)器, 該方法會(huì)直接創(chuàng)建一個(gè) timer 加到當(dāng)前的\
// runloop 中以默認(rèn)的方式執(zhí)行.
[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)配置一個(gè)長(zhǎng)生命周期的線程時(shí), 最好添加至少一個(gè)輸入源來(lái)接收消息, 雖然可以附加添加計(jì)時(shí)器的情況下進(jìn)入
runloop
,但一次性定時(shí)器通常觸發(fā)后通常會(huì)失效, 這會(huì)導(dǎo)致runloop
退出, 附加一個(gè)重復(fù)性的計(jì)時(shí)器可以使runloop
運(yùn)行更長(zhǎng)時(shí)間, 但這需要周期性的觸發(fā)定時(shí)器以喚醒runloop
, 這實(shí)際上是輪詢的另一種形式, 相反, 輸入源等待時(shí)間發(fā)生保證線程休眠直到事件發(fā)生.
Starting the Run Loop(啟動(dòng) runloop)
在應(yīng)用程序中, 只有子線程需要啟動(dòng)
runloop
,runloop
至少監(jiān)視一個(gè)輸入源或者timer
, 如果沒(méi)有一個(gè)附加源,runloop
會(huì)立即退出.
開啟runloop
的幾種方式:
- Unconditionally -- 沒(méi)有條件的
- - (void)run;
- With a set time limit -- 設(shè)置時(shí)間限制
- - (void)runUntilDate:(NSDate *)limitDate;
- In a particular mode -- 特定的
mode
- - (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
unconditionally :
在
NSDefaultRunLoopMode
中運(yùn)行反復(fù)調(diào)用runMode:beforeDate:
, 相當(dāng)于開啟了一個(gè)無(wú)限循環(huán).
最簡(jiǎn)單的方式, 但是也是最不可取的.
會(huì)使runloop
進(jìn)入永久循環(huán), 這使得你對(duì)通過(guò)runloop
自身的控制非常少.
可以添加和刪除輸入源和定時(shí)器, 但是停止
runloop
的唯一方法就是殺死它.
沒(méi)有辦法通過(guò)自定義mode
啟動(dòng)runloop
.
With a set time limit :
通過(guò)反復(fù)調(diào)用 runMode:beforeDate:
直到指定的過(guò)期日期,在 NSDefaultRunLoopMode
中運(yùn)行接收器溜嗜。
比
unconditionally
更好的啟動(dòng)runloop
的方法是使用超時(shí)值啟動(dòng)runloop
, 當(dāng)使用超時(shí)值時(shí),runloop
將會(huì)一直運(yùn)行直到事件到達(dá)或者超過(guò)超時(shí)值. 如果事件到達(dá),runloop
會(huì)在處理完事件后人后退出runloop
, 然后可以重新啟動(dòng)runloop
處理下一個(gè)事件; 如果是超時(shí), 則只需要重新啟動(dòng)runloop
或者在這個(gè)時(shí)刻執(zhí)行必要的清理工作.
In a particular mode :
使用特定的模式運(yùn)行運(yùn)行循環(huán)
模式限制了向運(yùn)行循環(huán)交付事件的源的類型, 從運(yùn)行循環(huán)中手動(dòng)刪除所有已知的輸入源和計(jì)時(shí)器并不保證運(yùn)行循環(huán)將立即退出宵膨。macOS可以根據(jù)需要安裝和刪除額外的輸入源,以處理針對(duì)接收方線程的請(qǐng)求炸宵。因此辟躏,這些源可以防止run循環(huán)退出。
Schedules the execution of a block :
- - (void)performInModes:(NSArray<NSRunLoopMode> *)modes block:(void (^)(void))block
- Schedules the execution of a block on the target run loop in given modes.
-- 在runloop
指定mode
中執(zhí)行block
- Schedules the execution of a block on the target run loop in given modes.
- - (void)performBlock:(void (^)(void))block;
- Schedules the execution of a block on the target run loop.
-- 在runloop
中執(zhí)行block
- Schedules the execution of a block on the target run loop.
running a runloop(官方示例):
- (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.
// 添加源或者 timer 進(jìn) runloop 以及其他一些設(shè)置
do {
// Start the run loop but return after each source is handled.
// 啟動(dòng) runloop, 但在處理每個(gè)源之后返回.
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.
// 如果一個(gè)源顯示的 stop runloop 或者沒(méi)有源或 timers, runloop 退出.
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)行 runloop
, 也就是說(shuō)可以調(diào)用 CFRunLoopRun
土全、CFRunLoopRunInMode
或任何 NSRunLoop
方法來(lái)從輸入源或計(jì)時(shí)器的處理程序例程中啟動(dòng)運(yùn)行循環(huán).
Exiting the Run Loop(退出 runloop)
有兩種方式可以讓 runloop 在處理事件之前退出:
- Configure the run loop to run with a timeout value. -- 配置
timeout
- Tell the run loop to stop. -- 告訴
runloop stop
.
如果是管理
runloop
, 則使用超時(shí)值當(dāng)然是首選. 指定超時(shí)值可以讓運(yùn)行循環(huán)在退出之前完成所有的正常處理, 包括向runloop observer
發(fā)送通知.
使用
CFRunLoopStop
函數(shù)顯式地停止 runloop 會(huì)產(chǎn)生類似超時(shí)的結(jié)果.runloop
在發(fā)送所有剩余的runloop
狀態(tài)通知后退出. 不同之處在于, 可以在無(wú)條件啟動(dòng)的runloop
中使用.
盡管刪除
runloop
的源和定時(shí)器可能也會(huì)使runloop
退出, 但是這個(gè)方法并不可靠, 因?yàn)橐恍┫到y(tǒng)例程將輸入源添加到runloop
中以處理所需的事件, 刪除的時(shí)候可能不知道這些源, 所以會(huì)阻止runloop
的退出.
Thread Safety and Run Loop Objects(線程安全和 runloop 對(duì)象)
線程安全性取決于使用哪個(gè)API來(lái)操作
runloop
.CoreFoundation
中的函數(shù)通常是線程安全的, 可以從任何線程調(diào)用. 但是, 如果您正在執(zhí)行 更改runloop
配置的操作, 最好還是盡可能從擁有runloop
的線程開始執(zhí)行.
NSRunLoop
類本身并不像它的核心基礎(chǔ)類那樣線程安全, 如果使用NSRunLoop
類來(lái)修改您的runloop
, 應(yīng)該只從擁有該runloop
的同一個(gè)線程進(jìn)行修改, 將輸入源或計(jì)時(shí)器添加到屬于不同線程的runloop
中可能會(huì)導(dǎo)致代碼崩潰或以意想不到的方式運(yùn)行.
Configuring Run Loop Sources(配置 runloop 源)
Defining a Custom Input Source(定義一個(gè)自定義輸入源)
定義一個(gè)自定義輸入源需要使用
CoreFoundation
配置源并且添加到runloop
中.
創(chuàng)建自定義輸入源需要如下定義:
- The information you want your input source to process.
- CFRunLoopSourceRef runLoopSource;
- NSMutableArray* commands;
- A scheduler routine to let interested clients know how to contact your input source.
- void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
- A handler routine to perform requests sent by any clients.
- void RunLoopSourcePerformRoutine (void *info);
- A cancellation routine to invalidate your input source.
- void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
自定義源是為了處理自定義信息, 實(shí)際配置也是靈活的, 大多數(shù)自定義輸入源總是需要
RunLoopSourceScheduleRoutine
/RunLoopSourcePerformRoutine
/RunLoopSourceCancelRoutine
三個(gè)關(guān)鍵的回調(diào)函數(shù). 你可以定義一種機(jī)制將數(shù)據(jù)傳遞到自定義的輸入源, 并用這個(gè)源與其他線程通信.
自定義輸入源的示例配置(官方示例):
應(yīng)用程序的主線程維護(hù)對(duì)輸入源的引用鸿脓、該輸入源已擁有 自定義命令緩沖區(qū) 以及runloop
.
當(dāng)主線程有一個(gè)任務(wù)要傳遞給工作線程時(shí), 它會(huì)向命令緩沖區(qū)發(fā)送一個(gè)命令以及工作線程啟動(dòng)任務(wù)所需的任何信息. (因?yàn)橹骶€程和工作線程的輸入源都可以訪問(wèn)命令緩沖區(qū), 所以必須是同步訪問(wèn)), 一旦發(fā)出命令, 主線程主線程向工作線程發(fā)出信號(hào), 并喚醒工作線程的
runloop
, 接收到喚醒命令后,runloop
會(huì)根據(jù)在命令緩沖區(qū)中找到的命令調(diào)用輸入源的回調(diào)函數(shù).
Operating a custom input source
Defining the Input Source(定義輸入源)
上圖顯示輸入源使用 Objective-C 對(duì)象來(lái)管理命令緩沖區(qū)并與
runloop
進(jìn)行協(xié)調(diào)抑钟。
下面是對(duì)這個(gè)對(duì)象的定義.
RunLoopSource
對(duì)象管理一個(gè)命令緩沖區(qū)涯曲,并使用該緩沖區(qū)接收來(lái)自其他線程的消息.RunLoopContext
實(shí)際上只是一個(gè)容器對(duì)象, 用于將RunLoopSource
對(duì)象和runloop
引用傳遞給應(yīng)用程序的主線程野哭。
The custom input source object definition(自定義輸入源對(duì)象定義):
@interface RunLoopSource : NSObject {
CFRunLoopSourceRef runLoopSource;
NSMutableArray* commands;
}
- (id)init;
- (void)addToCurrentRunLoop;
- (void)invalidate;
// Handler method
- (void)sourceFired;
// Client interface for registering commands to process
// 用于注冊(cè)要處理的命令的客戶端接口
- (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.
// RunLoopContext 是在注冊(cè)輸入源時(shí)使用的容器對(duì)象
@interface RunLoopContext : NSObject {
CFRunLoopRef runLoop;
RunLoopSource* source;
}
@property (readonly) CFRunLoopRef runLoop;
@property (readonly) RunLoopSource* source;
- (id)initWithSource:(RunLoopSource*)src andLoop:(CFRunLoopRef)loop;
@end
RunLoopSourceScheduleRoutine
雖然 OC代碼管理
source
的自定義數(shù)據(jù), 但是回調(diào)函數(shù)都是基于 C 的,RunLoopSourceScheduleRoutine
函數(shù)是在source
添加到runloop
中時(shí)回調(diào), 比如如下代碼: 因?yàn)檫@個(gè)輸入源只有一個(gè)客戶機(jī)(主線程), 所以它通過(guò)RunLoopSourceScheduleRoutine
函數(shù)發(fā)送一條消息, 將自己注冊(cè)到該線程上的應(yīng)用程序代理, 當(dāng)代理希望與source
通信時(shí), 就會(huì)使用RunLoopContext
對(duì)象中的信息進(jìn)行通信.
Scheduling a run loop source
void RunLoopSourceScheduleRoutine (void *info,
CFRunLoopRef rl,
CFStringRef mode) {
// 獲取 RunLoopSource 對(duì)象
RunLoopSource* obj = (RunLoopSource*)info;
// 設(shè)置應(yīng)用程序代理
AppDelegate* del = [AppDelegate sharedAppDelegate];
// 獲取 RunLoopContext 對(duì)象
RunLoopContext* theContext = [[RunLoopContext alloc]
initWithSource:obj andLoop:rl];
// 代理通過(guò) congtext 在主線程中執(zhí)行注冊(cè)方法.
[del performSelectorOnMainThread:@selector(registerSource:)
withObject:theContext waitUntilDone:NO];
}
RunLoopSourcePerformRoutine
輸入源發(fā)出一個(gè)信號(hào)用來(lái)處理自定義數(shù)據(jù)的回調(diào), 如下代碼顯示了與
RunLoopSource
相關(guān)聯(lián)的RunLoopSourcePerformRoutine
調(diào)用, 這個(gè)函數(shù)只是將開始工作的請(qǐng)求轉(zhuǎn)發(fā)給了sourceFired
方法, 然后該方法會(huì)處理在命令緩沖區(qū)中的命令.
Performing work in the input source
void RunLoopSourcePerformRoutine (void *info) {
RunLoopSource* obj = (RunLoopSource*)info;
// 調(diào)用 sourceFired
[obj sourceFired];
}
RunLoopSourceCancelRoutine
如果使用
CFRunLoopSourceInvalidate
函數(shù)將輸入源從runloop
中移除, 系統(tǒng)將調(diào)用RunLoopSourceCancelRoutine
函數(shù), 可以在這個(gè)函數(shù)中通知客戶端該輸入源已經(jīng)失效以讓客戶端移除對(duì)該源的引用, 下面的代碼顯示了RunLoopSource
的RunLoopSourceCancelRoutine
回調(diào)處理, 該函數(shù)給應(yīng)用程序代理發(fā)送了又一個(gè)RunLoopContext
對(duì)象, 但是這次是讓應(yīng)用程序代理移除對(duì)source
的引用.
Invalidating an input source
void RunLoopSourceCancelRoutine (void *info,
CFRunLoopRef rl,
CFStringRef mode) {
RunLoopSource* obj = (RunLoopSource*)info;
AppDelegate* del = [AppDelegate sharedAppDelegate];
// 獲取源對(duì)應(yīng)的 context
RunLoopContext* theContext = [[RunLoopContext alloc]
initWithSource:obj andLoop:rl];
// 讓代理移除 source 的引用
[del performSelectorOnMainThread:@selector(removeSource:)
withObject:theContext waitUntilDone:YES];
}
registerSource:
and removeSource:
方法在下面 Coordinating with Clients of the Input Source
.
Installing the Input Source on the Run Loop(添加源到 runloop)
如下代碼顯示了
RunLoopSource
的init
方法和addToCurrentRunLoop
方法,init
方法創(chuàng)建的CFRunLoopSourceRef
對(duì)象, 必須附加到runloop
中,RunLoopSource
會(huì)將自身作為上下文信息傳遞以至于回調(diào)函數(shù)可以用指針指向它, 直到工作線程調(diào)用addToCurrentRunLoop
方法才會(huì)添加源到runloop
, 同時(shí)會(huì)執(zhí)行RunLoopSourceScheduleRoutine
回調(diào), 一旦添加完成, 線程就會(huì) run therunloop
等待source
觸發(fā)事件.
Installing the run loop source
- (id)init {
// 創(chuàng)建一個(gè) sourceContext
CFRunLoopSourceContext context = {0, self,
NULL, NULL, NULL, NULL, NULL,
&RunLoopSourceScheduleRoutine,
RunLoopSourceCancelRoutine,
RunLoopSourcePerformRoutine};
// 創(chuàng)建 source
runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);
commands = [[NSMutableArray alloc] init];
return self;
}
- (void)addToCurrentRunLoop {
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
// 添加 source 到 runloop
CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
}
Coordinating with Clients of the Input Source(協(xié)調(diào)輸入源的客戶端)
想要?jiǎng)?chuàng)建的輸入源有用, 那么你需要操作它并從另一個(gè)線程發(fā)出信號(hào). 輸入源的意義在于讓與之相關(guān)的線程休眠直到源觸發(fā)事件, 這樣就需要讓其他線程知道并有方法去和它進(jìn)行通信.
通知客戶端的一個(gè)方法就是在首次將
source
添加到runloop
中時(shí), 發(fā)出注冊(cè)請(qǐng)求, 可以注冊(cè)任意想要注冊(cè)的客戶端, 或者可以向中央代理注冊(cè), 然后給到想要注冊(cè)的客戶端.
下面的代碼顯示了應(yīng)用程序代理注冊(cè)方法, 并在
RunLoopSource
對(duì)象執(zhí)行scheduler
方法被調(diào)用的時(shí)候執(zhí)行. 該方法接收由RunLoopSource
對(duì)象提供的RunLoopContext
對(duì)象并添加到源列表中; 也包括從runloop
中刪除源時(shí)的注銷源的回調(diào)方法.
Registering and removing an input source with the application delegate(使用應(yīng)用程序代理注冊(cè)和刪除輸入源)
- (void)registerSource:(RunLoopContext*)sourceInfo {
// 注冊(cè) context
[sourcesToPing addObject:sourceInfo];
}
- (void)removeSource:(RunLoopContext*)sourceInfo {
id objToRemove = nil;
// 遍歷找出對(duì)應(yīng)的 context
for (RunLoopContext* context in sourcesToPing) {
if ([context isEqual:sourceInfo]) {
objToRemove = context;
break;
}
}
// 刪除要?jiǎng)h除的 context
if (objToRemove)
[sourcesToPing removeObject:objToRemove];
}
Signaling the Input Source(向輸入源發(fā)送信號(hào))
在把數(shù)據(jù)傳遞給輸入源后(即自定義輸入源完成后), 客戶端必須向輸入源發(fā)送信號(hào)并喚醒它的
runloop
. 發(fā)送信號(hào)讓runloop
知道輸入源準(zhǔn)備好被處理. 由于發(fā)送信號(hào)的時(shí)候線程可能處于休眠狀態(tài), 所以應(yīng)該顯示的喚醒runloop
, 如果不喚醒runloop
的話, 會(huì)導(dǎo)致源事件處理被延時(shí).
客戶端準(zhǔn)備好處理他們加到緩沖區(qū)里的命令時(shí), 會(huì)調(diào)用RunLoopSource
的fireCommandsOnRunLoop
方法:
Waking up the run loop(喚醒 runloop
)
- (void)fireCommandsOnRunLoop:(CFRunLoopRef)runloop {
CFRunLoopSourceSignal(runLoopSource);
CFRunLoopWakeUp(runloop);
}
Configuring Timer Sources(配置 timer 源)
想要?jiǎng)?chuàng)建一個(gè)
timer
源, 必須創(chuàng)建一個(gè) timer 對(duì)象并且把它加到runloop
中,Cocoa
中的NSTimer
,CoreFoundation
中的CFRunLoopTimerRef
.NSTimer
內(nèi)部實(shí)現(xiàn)其實(shí)就是對(duì)CoreFoundation
的擴(kuò)展, 提供了便利特性, 類似創(chuàng)建和添加timer
使用同一個(gè)方法:
- scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
- scheduledTimerWithTimeInterval:invocation:repeats:
這兩個(gè)方法創(chuàng)建 timer 并將其以
NSDefaultRunLoopMode
默認(rèn)模式添加到當(dāng)前線程的runloop
中, 也可以通過(guò)NSRunLoop
的addTimer:forMode:
方法創(chuàng)建一個(gè) NSTimer 對(duì)象并手動(dòng)將其以不同 mode下添加到runloop
中.
Creating and scheduling timers using NSTimer(Cocoa 中創(chuàng)建)
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];
Creating and scheduling a timer using Core Foundation(CoreFoundation 中創(chuàng)建)
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
// use this structure to pass around any custom data\
// you needed for your timer.
// 可以根據(jù)需要為 timer 傳遞任何環(huán)境變量.
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
/*
CFRunLoopTimerRef CFRunLoopTimerCreate(CFAllocatorRef allocator,
CFAbsoluteTime fireDate,
CFTimeInterval interval,
CFOptionFlags flags,
CFIndex order,
CFRunLoopTimerCallBack callout,
CFRunLoopTimerContext *context);
*/
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
0.1,
0.3,
0,
0,
&myCFTimerCallback,
&context);
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);
Configuring a Port-Based Input Source(配置基于 port 的輸入源)
Cocoa
和CoreFoundation
都提供了基于端口的對(duì)象,用于線程或進(jìn)程之間的通信.
Configuring an NSMachPort Object(NSMachPort 對(duì)象)
要建立與本地連接的
NSMachPort
對(duì)象, 需要?jiǎng)?chuàng)建一個(gè)port
對(duì)象并將它添加到主線程的runloop
中, 當(dāng)啟動(dòng)子線程時(shí), 傳遞相同對(duì)象(port
)給子線程的入口函數(shù). 子線程將使用這個(gè)對(duì)象發(fā)送消息返回給主線程.
主線程啟動(dòng)子線程處理事件:
Implementing the Main Thread Code
Main thread launch method
- (void)launchThread {
// 創(chuàng)建 port
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.
// 添加到 runloop
[[NSRunLoop currentRunLoop] addPort:myPort
forMode:NSDefaultRunLoopMode];
// Detach the thread. Let the worker release the port.
// 將 port 傳遞給子線程, 由子線程進(jìn)行釋放
[NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:)
toTarget:[MyWorkerClass class] withObject:myPort];
}
}
為了在線程之間設(shè)置雙向通信, 可以讓工作線程在消息中向主線程發(fā)送自己的本地端口, 接收到消息讓主線程知道啟動(dòng)的子線程一切順利, 也提供了向該線程發(fā)送進(jìn)一步消息的方法幻件。
主線程的handlePortMessage:
方法如下, 該方法在數(shù)據(jù)到達(dá)線程的本地端口(port
)時(shí)調(diào)用. 當(dāng)消息到達(dá)時(shí), 該方法直接從端口檢索子線程的端口, 并將其保存起來(lái)供以后使用 :
#define kCheckinMessage 100 // 端口號(hào)
// 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.
}
}
Implementing the Secondary Thread Code
子線程必須配置并且需要指定 用來(lái)和將通信信息返回給主線程的 端口.
配置子線程, 代碼如下, 在為線程創(chuàng)建一個(gè)自動(dòng)釋放池之后, 然后創(chuàng)建了一個(gè)worker
對(duì)象以驅(qū)動(dòng)線程執(zhí)行,worker
的sendCheckinMessage:
方法為工作線程創(chuàng)建了一個(gè)本地port
并發(fā)送消息返回給主線程.
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.
// 設(shè)置當(dāng)前線程和主線程之間的連接
NSPort* distantPort = (NSPort*)inData;
MyWorkerClass* workerObj = [[self alloc] init];
// 這里傳入當(dāng)前線程的 port, 會(huì)被 workerObj 對(duì)象作為遠(yuǎn)程 port. \
// workerObj 自身也會(huì)創(chuàng)建一個(gè)自己的本地 port 用做通信.
[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];
}
// Worker thread check-in method
- (void)sendCheckinMessage:(NSPort*)outPort {
// Retain and save the remote port for future use.
// 引用并保存遠(yuǎn)程端口以供將來(lái)使用
[self setRemotePort:outPort];
// Create and configure the worker thread port.
// 創(chuàng)建并配置工作線程端口
NSPort* myPort = [NSMachPort port];
[myPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:myPort
forMode:NSDefaultRunLoopMode];
// Create the check-in message.
// 創(chuàng)建消息
NSPortMessage* messageObj = [[NSPortMessage alloc]
initWithSendPort:outPort
receivePort:myPort
components:nil];
if (messageObj) {
// Finish configuring the message and send it immediately.
// 完成消息配置并立即發(fā)送
[messageObj setMsgId:setMsgid:kCheckinMessage];
[messageObj sendBeforeDate:[NSDate date]];
}
}
當(dāng)使用
NSMachPort
時(shí), 本地線程和遠(yuǎn)程線程可以使用同一個(gè)端口對(duì)象進(jìn)行線程之間的單向通信. 也就是說(shuō), 一個(gè)線程創(chuàng)建的本地端口對(duì)象會(huì)變?yōu)榱硪粋€(gè)線程的遠(yuǎn)程端口對(duì)象.
Configuring an NSMessagePort Object(配置一個(gè) NSMessagePort)
使用
NSMessagePort
對(duì)象建立本地連接, 不能簡(jiǎn)單的在線程間傳遞port
對(duì)象. 必須通過(guò)name
獲取, 在Cocoa
中需要用一個(gè)特殊的name
注冊(cè)本地端口, 然后傳遞給遠(yuǎn)程線程以便它能夠獲得適當(dāng)?shù)?port 對(duì)象進(jìn)行通信.
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];
Configuring a Port-Based Input Source in Core Foundation(CoreFoundation 中創(chuàng)建 source1)
下面的代碼顯示了主線程啟動(dòng)工作線程, 首先設(shè)置了一個(gè)
CFMessagePortRef
對(duì)象監(jiān)聽來(lái)自工作線程的消息. 工作線程需要port
的name
來(lái)建立連接, 以便將字符串值傳遞給工作線程的入口函數(shù),name
通常唯一, 否則會(huì)發(fā)生沖突.
Attaching a Core Foundation message port to a new thread(添加 CoreFoundation
的 message port
到一個(gè)新的線程):
#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.
// 創(chuàng)建線程并繼續(xù)處理
MPTaskID taskID;
return(MPCreateTask(&ServerThreadEntryPoint,
(void*)myPortName,
kThreadStackSize,
NULL,
NULL,
NULL,
0,
&taskID));
}
MainThreadResponseHandler
主線程回調(diào)處理函數(shù)
#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);
// 獲取 data 信息, 解析 port name
CFDataGetBytes(data, CFRangeMake(0, bufferLength), buffer);
threadPortName = CFStringCreateWithBytes (NULL,
buffer,
bufferLength,
kCFStringEncodingASCII,
FALSE);
// You must obtain a remote message port by name.
// 通過(guò) name 獲得遠(yuǎn)程 message port
messagePort = CFMessagePortCreateRemote(NULL,
(CFStringRef)threadPortName);
if (messagePort) {
// Retain and save the thread’s comm port for future reference
// 保存 message port
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)建自己的端口并登錄, 下面是工作線程的入口函數(shù), 該函數(shù)獲取主線程的
port name
并使用它創(chuàng)建回主線程的遠(yuǎn)程連接, 然后給自己創(chuàng)建一個(gè)port
并添加到當(dāng)前線程的runloop
中, 并向主線程發(fā)送包含本地端口名稱的消息.
Setting up the thread structures(設(shè)置線程結(jié)構(gòu)) -- 方法在主線程創(chuàng)建新線程時(shí)調(diào)用.
OSStatus ServerThreadEntryPoint(void* param) {
// Create the remote port to the main thread.
// 創(chuàng)建主線程的遠(yuǎn)程 port
CFMessagePortRef mainThreadPort;
CFStringRef portName = (CFStringRef)param;
mainThreadPort = CFMessagePortCreateRemote(NULL, portName);
// Free the string that was passed in param.
// 參數(shù)釋放
CFRelease(portName);
// Create a port for the worker thread.
// 創(chuàng)建當(dāng)前線程 port name
CFStringRef myPortName = CFStringCreateWithFormat(NULL,
NULL,
CFSTR("com.MyApp.Thread-%d"),
MPCurrentTaskID());
// Store the port in this thread’s context info for later reference.
// 保存到當(dāng)前線程的 context 以便之后使用
CFMessagePortContext context = {0, mainThreadPort, NULL, NULL, NULL};
Boolean shouldFreeInfo; // 標(biāo)記是否需要釋放
Boolean shouldAbort = TRUE;
// 創(chuàng)建 message port
CFMessagePortRef myPort = CFMessagePortCreateLocal(NULL,
myPortName,
&ProcessClientRequest,
&context,
&shouldFreeInfo);
if (shouldFreeInfo) {
// Couldn't create a local port, so kill the thread.
MPExit(0);
}
// 創(chuàng)建 source
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.
// 添加 source 到 runloop
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);
// 發(fā)送消息請(qǐng)求
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)入運(yùn)行循環(huán)拨黔,所有發(fā)送到線程端口的未來(lái)事件都由
ProcessClientRequest
函數(shù)處理。該函數(shù)的實(shí)現(xiàn)取決于線程執(zhí)行的工作類型.