Run Loops

寫在前面

閱讀 Apple Deleloper Run Loops 的筆記。

簡介

Run Loops 是和線程息息相關(guān)的基礎(chǔ)組件。
顧名思義,它就是一個循環(huán),目的是:當(dāng)有工作時,讓線程忙碌起來;當(dāng)沒有工作時,讓線程休眠。

Run loop 的管理不是完全自動化的,開發(fā)者仍然必須設(shè)計(jì)線程相關(guān)代碼,讓 run loop 在合適時間開啟。

Cocoa 和 Core Foundation 都提供了 run loop 對象輔助配置和管理。
開發(fā)者不需要再創(chuàng)建這些對象。每個線程(包括主線程)都有一個 run loop 對象與之關(guān)聯(lián)。
App 在啟動時,會在主線程上設(shè)置和啟動 run loop。
只有次要線程需要手動啟動 run loop。

Structure of a run loop and its sources

如圖所示,run loop 接收2種類型數(shù)據(jù):

  1. Input sources : 傳遞異步事件,經(jīng)常是來自其他線程或不同應(yīng)用的信息。
  2. Timer sources : 傳遞同步事件,定時或重復(fù)發(fā)生。

除了處理資源,run loops 也創(chuàng)建了關(guān)于 run loop 行為的通知。
注冊一個 run loop observer 可以接受到這些通知,并且可以在線程上使用它們。
可以使用 Core Foundation 來安裝 run loop observer 到線程上。

Run Loop Modes

一個 run loop mode 是 input sources 和 timer sources 的集合。
傳輸過程中,只有關(guān)聯(lián)到 mode 的 source 才允許傳遞事件。
Cocoa 和 Core Foundation 都定義了默認(rèn) mode 和一些常用 mode。
開發(fā)者也可以自定義 mode。
使用 mode 可以過濾事件。

預(yù)置的 mode

  • Default
    NSConnectionReplyMode(Cocoa)
    kCFRunLoopDefaultMode(Core Foundation)
    默認(rèn) mode ,在大部分操作中使用,大多數(shù)時候,應(yīng)該使用這個 mode 去開啟 run loop 和配置 input source。

  • Connection
    NSConnectionReplyMode(Cocoa)
    Cocoa 使用這個 mode,結(jié)合 NSConnection 對象去管理回復(fù),開發(fā)者很少需要使用它。

  • Modal
    NSModalPanelRunLoopMode(Cocoa)
    Cocoa 使用這個 mode 去標(biāo)記模態(tài)面板。

  • Event tracking
    NSEventTrackingRunLoopMode(Cocoa)
    當(dāng)用戶接口追蹤循環(huán)過程中,如鼠標(biāo)拖拽,Cocoa 使用這個 mode 去限制即將到來的事件。

  • Common modes
    NSRunLoopCommonModes(Cocoa)
    kCFRunLoopCommonModes(Core Foundation)
    這是一個可以配置的組合,它已經(jīng)包含了常用 mode ,開發(fā)者也可以自己添加自定義 mode 。

Input Sources

Input source 異步地傳遞事件給線程。
事件資源依賴于 input source 的類型,一般分為2類:

  1. port_based 資源:管理應(yīng)用的 Mach ports。
  2. 自定義 input source:管理自定義事件資源。

無須關(guān)心 run loop 的類型,系統(tǒng)一般會實(shí)現(xiàn)2種類型。

這2種資源唯一的區(qū)別在于它們是如何被發(fā)信號的:
port_based 資源由內(nèi)核自動發(fā)信號,而自定義資源必須手動由其他線程完成。

如果某個 input source 不是當(dāng)前運(yùn)行中的 mode ,它創(chuàng)建的任何事件都會被掛起,直到 run loop 運(yùn)行到合適的 mode 。

Port_Based Sources

Cocoa 和 Core Foundation 內(nèi)置創(chuàng)建這類 source 的支持。
比如說,開發(fā)者從不需要直接創(chuàng)建一個 input source ,只需要創(chuàng)建一個端口對象,然后使用 NSPort 的方法,添加端口到 run loop。端口對象會創(chuàng)建并配置好需要的 input source。

在 Core Foundation 中,你必須手動創(chuàng)建端口和它的 run loop 資源。
需要使用關(guān)聯(lián)特定端口類型(CFMachPortRef, CFMessagePortRef, CFSocketRef)的方法來創(chuàng)建合適的對象。

Custom Input Sources

使用 Core Foundation 中關(guān)聯(lián)了 CFRunLoopSourceRef 類型的方法,來創(chuàng)建 custom input source,并通過很多回調(diào)方法來配置它。

為了定義 custom input source 的行為,必須定制事件傳遞機(jī)制。

Cocoa Perform Selector Sources

除了 port_based sources,Cocoa 定義了能在任何線程上 perform selector 的 input source。
跟 port_based sources 一樣,perform selector 請求會在目標(biāo)線程上排好序。
不同的是,它在執(zhí)行后,需要移除自身。

Timer Sources

Timer sources 在預(yù)設(shè)的時間點(diǎn),同步傳遞事件給線程。

Timer 不是一個實(shí)時機(jī)制,跟 input sources 一樣,它與 run loop 中特定的 modes 關(guān)聯(lián),如果不在 run loop 當(dāng)前的 modes 中,那么它不會被執(zhí)行。

Run Loop observers

Run Loop observers 事件類型:

  • The entrance to the run loop.
  • When the run loop is about to process a timer.
  • When the run loop is about to process an input source.
  • When the run loop is about to go to sleep.
  • When the run loop has woken up, but before it has processed the event that woke it up.
  • The exit from the run loop.

Run Loop 事件順序:

  1. Notify observers that the run loop has been entered.
  2. Notify observers that any ready timers are about to fire.
  3. Notify observers that any input sources that are not port based are about to fire.
  4. Fire any non-port-based input sources that are ready to fire.
  5. If a port-based input source is ready and waiting to fire, process the event immediately. Go to 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:
    • An event arrives for a port-based input source.
    • A timer fires.
    • The timeout value set for the run loop expires.
    • 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.
    • If an input source fired, deliver the event.
    • If the run loop was explicitly woken up but has not yet timed out, restart the loop. Go to step 2.
  10. Notify observers that the run loop has exited.

因?yàn)?timer 和 input sources 的監(jiān)聽通知會在這些事件實(shí)際發(fā)生前被傳遞。
所以,如果時間要求比較苛刻的話,可使用 sleepawake-from-sleep 通知來確認(rèn)時間。

何時需要使用 Run Loop?

只有創(chuàng)建次要線程時,才需要顯式運(yùn)行一個 run loop。
主線程上的 run loop 是至關(guān)重要的。
因此,app frameworks 提供了運(yùn)行 main application loop 的代碼,并且自動啟動 run loop。

在以下情況,你需要去啟動一個 run loop

  • Use ports or custom input sources to communicate with other threads.
  • Use timers on the thread.
  • Use any of the performSelector… methods in a Cocoa application.
  • Keep the thread around to perform periodic tasks.

使用 Run Loop 對象

一個 run loop 對象提供添加 input sources, timers, run-loop observers,并運(yùn)行它們的主要接口,每個線程都有一個關(guān)聯(lián)的 run loop 對象。Cocoa 中,它是 NSRunLoop 的實(shí)例,更底層的,它是一個指向 CFRunLoopRef opaque type 的指針。

獲取 Run Loop 對象

方法:

  1. Cocoa 應(yīng)用,使用 NSRunLoop 的 currentRunLoop。
  2. 使用 CFRunLoopGetCurrent 方法。

雖然它們不是無縫連接的類型,但可以通過 NSRunLoop 里的 getCFRunLoop 方法來獲得一個 CFRunLoopRef opaque type,因?yàn)樗鼈兌际且猛?run loop,所以可以隨意使用其中之一。

配置 Run Loop

運(yùn)行一個 run loop 前,它必須至少添加了一個 input source 或 timer,否則無法運(yùn)行它。

除了 sources,還可以安裝 run loop observers,然后使用它們來判斷 run loop 的運(yùn)行狀態(tài)。

當(dāng)配置一個長時間存活的 run loop,最好得添加至少一個 input source 來接收信息。因?yàn)榧词固砑右粋€ timer 也可以運(yùn)行 run loop,但一旦 timer 結(jié)束,這種方式也就失效了,這樣就會造成 run loop 退出。

啟動 Run Loop

只需要在次要線程中啟動 Run Loop,有以下方式啟動 run loop

  • Unconditionally
    最簡單方式,可以添加或移除 input sources, timers,但只能通過 kill 來停止它,而且也無法運(yùn)行在“自定義 mode”。

  • With a set time limit
    比 Unconditionally 更好的方式是,運(yùn)行一個帶有 timeout 變量的 run loop。

  • In a particular mode
    除了 timeout 變量,可以使用特定 Mode 運(yùn)行。

退出 Run Loop

方式

  • Configure the run loop to run with a timeout value.
    比較推薦的方式,在退出之前,run loop 處理完該處理的。
  • Tell the run loop to stop.
    調(diào)用 CFRunLoopStop 方法也能起到使用 timeout 變量同樣的效果,不同之處在于,可以在 Unconditionally 啟動的 run loops 上使用這項(xiàng)技術(shù)。

雖然移除 input sources 和 timers 也有可能會造成 run loop 退出,但這不是停止 run loop 的可靠方式。系統(tǒng)在需要處理事件時,會添加 input sources 到 run loop 中,因?yàn)殚_發(fā)者無法知道這些 input sources,所以無法移除它們,這就無法使 run loop 退出。

線程安全和 Run Loop 對象

是否安全取決于使用什么 API 來操作 run loop。

Core Foundation 中的方法一般是線程安全的,可在任何線程中調(diào)用,但最好還是在持有 run loop 的線程上調(diào)用。
Cocoa NSRunLoop 不是線程安全的,使用時,要盡量在同一持有 run loop 的線程中使用。

總結(jié)

Run Loop 是一個特殊的循環(huán):有工作時,讓線程忙碌;當(dāng)沒有工作時,讓線程休眠。

它接收的數(shù)據(jù)類型:

  1. Input Sources
  • Port-Based Input Sources
  • Custom Input Sources
  • Cocoa Perform Selector Sources
  1. Timer Sources

不同的 Input Sources 和 Timer Sources 的集合就是 Run Loop Modes。

除了 Sources 外,還能注冊 Run Loop Observer 來接收 Run Loop 行為的通知。

只有在次級線程上,才需要使用 Run Loop 對象來管理 Run Loop。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. 查看服務(wù)器最后重啟時間 2. 根據(jù)文件名查找文件 3. 查看進(jìn)程信息 4. 使用scp復(fù)制本地文件到遠(yuǎn)程服務(wù)...
    任無名F閱讀 266評論 0 0
  • 原文地址:客戶端生成七牛上傳token 在使用七牛iOS SDK上傳圖片時需要用到上傳的token,雖然七牛建議t...
    宮城_閱讀 4,102評論 1 8
  • 涵寶和云寶都有喜歡的小伙伴,涵寶喜歡他們班的峻瑜同學(xué),云寶喜歡他們班的晨軒同學(xué)。 有一天云寶自顧自得說:等我長大了...
    pan02閱讀 288評論 0 0
  • 01. 大致算了下,來到這學(xué)校已經(jīng)567天了,2016年7月4日來到學(xué)校,2016年的180天學(xué)校生活和2017年...
    路卡利歐Mega閱讀 101評論 0 0

友情鏈接更多精彩內(nèi)容