Runloop

注: 本文對(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 官方文檔.

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ù)間隔.
  • 輸入源(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)退出.

    RunLoop 與源.png

  • observers, runloop 狀態(tài)觀察者, runloop 會(huì)給 run loop observers 發(fā)送 runloop 狀態(tài)的通知, 使觀察者在線程上執(zhí)行額外的處理, 在 CoreFoundation 中可以添加 run loop observers (CFRunLoopAddObserver()).

Run Loop Modes

  • Run Loop Modesrunloop 要監(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)面板下可能以 modal mode 運(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(基于端口的輸入源)

CocoaCoreFoundation 為使用 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年扩、CFMessagePortRefCFSocketRef)相關(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)處理等待的事件并為任何附加的觀察者生成通知, 具體順序如下:

  1. Notify observers that the run loop has been entered. -- 通知觀察者 runloop 已經(jīng)進(jìn)入循環(huán)
  2. Notify observers that any ready timers are about to fire. -- 通知觀察者 timers 將要觸發(fā)事件
  3. Notify observers that any input sources that are not port based are about to fire. -- 通知觀察者 不是基于端口的輸入源 將要觸發(fā)
  4. Fire any non-port-based input sources that are ready to fire. -- 通知觀察者 不是基于端口的輸入源 觸發(fā)了
  5. 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.
  6. Notify observers that the thread is about to sleep. -- 通知觀察者 線程將要休眠
  7. 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. -- 被顯式地喚醒
  8. Notify observers that the thread just woke up. -- 通知觀察者 線程剛剛被喚醒
  9. 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.
  10. Notify observers that the run loop has exited. -- 通知觀察者 runloop 已經(jīng)退出.

對(duì)應(yīng)流程如下圖:

RunLoop 執(zhí)行流程.png

由于計(jì)時(shí)器和輸入源的觀察者通知是在這些事件實(shí)際發(fā)生之前交付的叹括,因此通知的時(shí)間和實(shí)際事件的時(shí)間之間可能存在差距算墨。如果這些事件之間的時(shí)間非常關(guān)鍵,則可以使用 sleepsleep-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, and run-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
  • - (void)performBlock:(void (^)(void))block;
    • Schedules the execution of a block on the target run loop.
      -- 在 runloop 中執(zhí)行 block

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

自定義源.png

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ì)該源的引用, 下面的代碼顯示了 RunLoopSourceRunLoopSourceCancelRoutine 回調(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)

如下代碼顯示了 RunLoopSourceinit 方法和 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 the runloop 等待 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)用 RunLoopSourcefireCommandsOnRunLoop 方法:

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ò) NSRunLoopaddTimer: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 的輸入源)

CocoaCoreFoundation 都提供了基于端口的對(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í)行, workersendCheckinMessage: 方法為工作線程創(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)自工作線程的消息. 工作線程需要 portname 來(lái)建立連接, 以便將字符串值傳遞給工作線程的入口函數(shù), name 通常唯一, 否則會(huì)發(fā)生沖突.

Attaching a Core Foundation message port to a new thread(添加 CoreFoundationmessage 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í)行的工作類型.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绰沥,一起剝皮案震驚了整個(gè)濱河市篱蝇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌徽曲,老刑警劉巖零截,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異秃臣,居然都是意外死亡涧衙,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門奥此,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)弧哎,“玉大人,你說(shuō)我怎么就攤上這事稚虎〕纺郏” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蠢终,是天一觀的道長(zhǎng)序攘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)寻拂,這世上最難降的妖魔是什么程奠? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮兜喻,結(jié)果婚禮上梦染,老公的妹妹穿的比我還像新娘。我一直安慰自己朴皆,他們只是感情好帕识,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遂铡,像睡著了一般肮疗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扒接,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天伪货,我揣著相機(jī)與錄音们衙,去河邊找鬼。 笑死碱呼,一個(gè)胖子當(dāng)著我的面吹牛蒙挑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播愚臀,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼忆蚀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了姑裂?” 一聲冷哼從身側(cè)響起馋袜,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎舶斧,沒(méi)想到半個(gè)月后欣鳖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茴厉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年泽台,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呀忧。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡师痕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出而账,到底是詐尸還是另有隱情胰坟,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布泞辐,位于F島的核電站笔横,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏咐吼。R本人自食惡果不足惜吹缔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锯茄。 院中可真熱鬧厢塘,春花似錦、人聲如沸肌幽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)喂急。三九已至格嘁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間廊移,已是汗流浹背糕簿。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工探入, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人懂诗。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓蜂嗽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親响禽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子徒爹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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