RunLoop官方文檔翻譯

Run Loops

Run loops是與線程相關(guān)的基礎(chǔ)框架的一部分。一個(gè)run loop是一個(gè)循環(huán),在這個(gè)循環(huán)中你可以分配任務(wù),并協(xié)調(diào)安排接收到的各種事件。run loop存在的目的是使線程在有任務(wù)的時(shí)候處于工作狀態(tài),并且當(dāng)沒有任務(wù)的時(shí)候使線程處于休眠狀態(tài).
Run loop的管理不完全是自動(dòng)的,你仍然需要設(shè)計(jì)跟線程有關(guān)的代碼,以便在適當(dāng)?shù)臅r(shí)候啟動(dòng)運(yùn)行循環(huán),并響應(yīng)傳入的事件。
Cocoa和Core Foundation都提供一些與run loop相關(guān)的對象來幫助你配置和管理線程的run loop。你的應(yīng)用程序不需要顯式的創(chuàng)建這些對象;每一個(gè)線程,包括主線程都有一個(gè)相關(guān)聯(lián)的run loop對象。子線程的run loop對象需要手動(dòng)開啟,而系統(tǒng)的app framework會(huì)在app啟動(dòng)時(shí)候 自動(dòng)設(shè)置并開啟主線程的run loop。

Run Loop剖析

run loop就像它的名字含義一樣。它是一個(gè)線程可以進(jìn)入其中并用來運(yùn)行事件處理程序以響應(yīng)傳入事件的循環(huán)。我們可以通過代碼控制循環(huán),換句話說,我們可以在代碼中用for循環(huán)或者while循環(huán)控制run loop。在循環(huán)內(nèi)部,我們可以用run loop對象去運(yùn)行事件處理代碼,這些代碼可以接收外部事件,并且調(diào)用已經(jīng)綁定到run loop對象的回調(diào)函數(shù)。
run loop接收來自兩種不同類型的源的事件。輸入源提供異步事件,通常是來自另一個(gè)線程或不同應(yīng)用程序的消息。定時(shí)器源提供同步事件,發(fā)生在預(yù)定時(shí)間或重復(fù)間隔。這兩種類型的源都使用應(yīng)用程序特別指定的處理程序來處理到達(dá)的事件。
下圖顯示了run loop和各種源的結(jié)構(gòu)。輸入源將異步事件傳遞給相應(yīng)的處理程序,并導(dǎo)致runUntilDate:方法(在與線程關(guān)聯(lián)的NSRunLoop對象上調(diào)用)退出。定時(shí)器源將事件傳遞到其處理程序例程,但不會(huì)導(dǎo)致run loop退出。


圖片.png

除了處理輸入源之外,run loop同時(shí)也生成了有關(guān)run loop各種狀態(tài)的通知。注冊run loop的observers可以收到這些通知,并且可以在線程上做一些其他操作。通過Core Foundation框架可以在線程上添加observer

Run Loop Modes

Run Loop Modes是要監(jiān)視的輸入源和定時(shí)器的集合,以及要通知的運(yùn)行循環(huán)observer的集合。每次運(yùn)行run loop時(shí),都需要(顯式或隱式)指定特定的“模式”來運(yùn)行。在這次run loop過程中,只有與該模式相關(guān)的源會(huì)被監(jiān)聽,并允許其發(fā)送事件。 (類似地,只有與該模式相關(guān)的observer才會(huì)被通知run loop的狀態(tài)。)與其他模式相關(guān)的源會(huì)保留發(fā)送給它們事件,直到隨后以適當(dāng)?shù)哪J竭\(yùn)行。
在我們的代碼中,我們可以通過名字來區(qū)分模式。 Cocoa和Core Foundation都定義了一個(gè)默認(rèn)模式和若干個(gè)常用模式,以及用于在代碼中標(biāo)志這些模式的字符串。我們可以通過自定義一個(gè)字符串作為名字來定義模式。雖然這個(gè)字符串可以是任意的,但是模式的內(nèi)容卻不是任意的。為了確保創(chuàng)建的模式(models)可用,必須向其中添加至少一個(gè)或多個(gè)輸入源(input sources),定時(shí)器(timers),或者run loop觀察者(run loop observer)。
我們可以使用模式在特定的運(yùn)行循環(huán)模式下過濾掉不需要的源事件。在大多數(shù)情況下,我們是在系統(tǒng)已經(jīng)定義好的"default"模式下。然而,模態(tài)面板可能以"模態(tài)"模式運(yùn)行(注:這段不是很懂,不了解模態(tài)面板和模態(tài)模式)。在這種模式下,只有與該模式相關(guān)的源才會(huì)將事件傳遞給線程。對于其他次級線程,我們可以通過自定義models用來阻值低優(yōu)先級的源在限時(shí)操作中傳遞事件。

注意:模式基于事件的來源而不是事件的類型進(jìn)行區(qū)分。 例如,你不會(huì)使用模式來匹配手指觸摸事件或鍵盤輸入事件。 你可以使用模式來監(jiān)聽一組不同的端口,計(jì)時(shí)器臨時(shí)的暫停,或者更改當(dāng)前正在監(jiān)視的源(source)以及run loop observer。

輸入源(input source)

輸入源異步發(fā)送事件到線程中。輸入源的類型決定了事件的類型,通常有兩種事件。Port-based輸入源監(jiān)聽?wèi)?yīng)用的Mach端口。自定義輸入源監(jiān)聽自定義事件。就run loop而言,輸入源是基于Mach端口或者其他端口并不重要。
當(dāng)創(chuàng)建一個(gè)輸入源后,可以將它添加run loop的一個(gè)或多個(gè)model中。在任何時(shí)刻,models決定了哪些輸入源處于被監(jiān)聽狀態(tài)。大多數(shù)時(shí)間,run loop處于default model下,但是可以切換到custom model下。如果一個(gè)輸入源沒有被添加到當(dāng)前正在運(yùn)行的模式下,它的所有事件都會(huì)被阻塞,直到run loop切換到該模式下。
下面幾部分介紹了其中一些輸入源(input source)

Port-Based Sources

Cocoa框架和Core Foundation框架內(nèi)部都提供了與port相關(guān)的對象和方法,可以用來創(chuàng)建輸入源。在Cocoa框架中,不用直接創(chuàng)建輸入源,只需要?jiǎng)?chuàng)建一個(gè)port對象,并使用NSPort提供的方法將這個(gè)對象加入到run loop即可。port對象會(huì)為你管理輸入源的創(chuàng)建和配置。
在Core Foundation中,你必須手動(dòng)創(chuàng)建port和它的輸入源。在這兩種情況下,你可以使用與port類型(CFMachPortRef, CFMessagePortRef, or CFSocketRef)相關(guān)的函數(shù)去創(chuàng)建合適的對象。

Custom Input Sources

為了創(chuàng)建一個(gè)自定義輸入源,你必須使用Core Foundation中與CFRunLoopSourceRef相關(guān)的函數(shù)。你使用幾個(gè)回調(diào)函數(shù)來配置一個(gè)輸入源。Core Foundation框架會(huì)在不同時(shí)刻調(diào)用這些函數(shù)以配置源、處理任何輸入事件,并且當(dāng)source被移除run loop時(shí)候會(huì)被自動(dòng)釋放掉。
除了需要定義回調(diào)函數(shù)之外,還必須定義事件傳遞機(jī)制。這部分的源代碼在單獨(dú)的線程上運(yùn)行,負(fù)責(zé)為輸入源提供數(shù)據(jù),并在數(shù)據(jù)準(zhǔn)備好處理時(shí)用信號通知它。 事件傳遞機(jī)制取決于你,但不必過于復(fù)雜。

Cocoa Perform Selector Sources

除了基于端口的源之外,Cocoa還定義了一個(gè)自定義輸入源,允許你在任何線程上執(zhí)行選擇器。 像基于端口的源一樣,執(zhí)行選擇器請求在目標(biāo)線程上被序列化,減輕了在一個(gè)線程上運(yùn)行多個(gè)方法時(shí)可能發(fā)生的許多同步問題。 與基于端口的源不同,執(zhí)行選擇器源在執(zhí)行選擇器之后將自身從運(yùn)行循環(huán)中移除。

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

在另一個(gè)線程上執(zhí)行選擇器時(shí),目標(biāo)線程必須有一個(gè)活動(dòng)的運(yùn)行循環(huán)。 對于你創(chuàng)建的線程,這意味著等待直到你的代碼明確地啟動(dòng)運(yùn)行循環(huán)。 因?yàn)橹骶€程啟動(dòng)自己的運(yùn)行循環(huán),所以只要應(yīng)用程序調(diào)用應(yīng)用程序委托的applicationDidFinishLaunching:方法,就可以開始在該線程上發(fā)出調(diào)用。 運(yùn)行循環(huán)每次通過循環(huán)處理所有排隊(duì)的執(zhí)行選擇器調(diào)用,而不是在每個(gè)循環(huán)迭代中處理一個(gè)。

timers

定時(shí)器源在未來的預(yù)設(shè)時(shí)間將事件同步傳遞給你的線程。定時(shí)器是線程通知自己做某事的一種方式。例如,search field可以使用定時(shí)器來在用戶的連續(xù)輸入之間經(jīng)過一定的時(shí)間后啟動(dòng)自動(dòng)搜索。這個(gè)延遲時(shí)間的使用使用戶有機(jī)會(huì)在開始搜索之前盡可能多地輸入想要的搜索字符串
雖然它會(huì)生成基于時(shí)間的通知,但計(jì)時(shí)器不是實(shí)時(shí)機(jī)制。像輸入源一樣,定時(shí)器與run loop的特定模式相關(guān)聯(lián)。如果一個(gè)定時(shí)器不在當(dāng)前被運(yùn)行循環(huán)監(jiān)視的模式下,它不會(huì)被觸發(fā),直到處于定時(shí)器支持的一種模式下運(yùn)行時(shí)才會(huì)被觸發(fā)。同樣的,如果一個(gè)定時(shí)器在run loop處于執(zhí)行處理程序的過程中觸發(fā),定時(shí)器會(huì)等待下一次通過run loop來調(diào)用它的處理程序。如果run loop根本沒有運(yùn)行,定時(shí)器不會(huì)啟動(dòng)。
你可以將定時(shí)器配置為僅生成一次或重復(fù)生成事件,重復(fù)的計(jì)時(shí)器會(huì)根據(jù)預(yù)訂的觸發(fā)時(shí)間而不是實(shí)際的觸發(fā)時(shí)間自動(dòng)重新安排時(shí)間。例如,如果計(jì)時(shí)器在某個(gè)時(shí)間以及之后的每5秒鐘觸發(fā)一次,則即使實(shí)際的觸發(fā)被延遲,計(jì)劃的觸發(fā)時(shí)間將總是以原來的5秒的時(shí)間間隔下降。如果觸發(fā)時(shí)間延遲過多,以至于錯(cuò)過了一個(gè)或多個(gè)預(yù)定的觸發(fā)時(shí)間,則計(jì)時(shí)器僅在錯(cuò)過的時(shí)間段內(nèi)觸發(fā)一次。在錯(cuò)過的時(shí)間觸發(fā)之后,定時(shí)器重新計(jì)劃下一個(gè)預(yù)定的觸發(fā)時(shí)間。

Run Loop Observers

與源不同,run loop observers是當(dāng)某些同步事件或者異步事件發(fā)生的時(shí)候被觸發(fā)。run loop observers在run loop中的一個(gè)特定狀態(tài)被觸發(fā)。你可以通過run loop observer去讓線程在run loop的開始狀態(tài)或者休眠狀態(tài)執(zhí)行特定的動(dòng)作。你可以將run loop observer與run loop中的以下幾種狀態(tài)關(guān)聯(lián):
1.即將進(jìn)入run loop
2.run loop即將處理timer
3.run loop即將處理輸入源
4.run loop即將進(jìn)入休眠狀態(tài)
5.run loop已經(jīng)被喚醒,但還沒處理喚醒它的事件
6.run loop退出循環(huán)
你可以通過Core Foundation去添加run loop observer。如果要?jiǎng)?chuàng)建run loop observer,首先創(chuàng)建CFRunLoopObserverRef的實(shí)例。這個(gè)實(shí)例會(huì)將你自定義的回調(diào)函數(shù)與它關(guān)心的run loop狀態(tài)關(guān)聯(lián)。
同timer類似,run loop observer可以被一次或重復(fù)使用。一個(gè)一次性的run loop observer會(huì)在它觸發(fā)后將自己從run loop移除。但是重復(fù)使用的observer不會(huì)這樣。你可以在創(chuàng)建observer的時(shí)候指定它是否重復(fù)。

run loop事件隊(duì)列

每次運(yùn)行run loop時(shí)候,線程的run loop會(huì)處理即將發(fā)生的事件,并且會(huì)為所有observer生成通知。它會(huì)以特定的順序執(zhí)行這些操作,順序如下:
1.通知觀察者run loop已經(jīng)進(jìn)入循環(huán)
2.通知觀察者任何準(zhǔn)備好的timer即將被觸發(fā)
3.通知觀察者任何port端口之外的輸入源將要觸發(fā)
4.觸發(fā)任何非基于端口(source0)的輸入源事件
5.如果有基于端口的輸入源事件(source1),立刻處理這些事件,并跳轉(zhuǎn)到第9步
6.通知觀察者,線程將要休眠
7.將線程置于休眠狀態(tài),直到下列事件發(fā)生:

  • port端口事件到達(dá)
  • timer 觸發(fā)
  • 到達(dá)run loop超時(shí)時(shí)間
  • run loop被顯示喚醒

8.通知觀察者線程剛被喚醒
9.處理喚醒run loop的事件

  • 如果是用戶定義的timer觸發(fā)時(shí)間到了,處理timer事件,并重新開啟run loop。跳轉(zhuǎn)到第二步
  • 如果是輸入源事件,處理這個(gè)事件
  • 如果run loop是被顯式調(diào)用,并且還未到run loop的超時(shí)時(shí)間,重新開啟runloop 跳轉(zhuǎn)到第二步

10.通知observer,run loop已經(jīng)退出循環(huán)。


圖片.png

由于timer和輸入源的觀察者是先收到通知,在觸發(fā)事件。所以通知的時(shí)間和實(shí)際事件觸發(fā)的時(shí)間之間可能存在差距。如果這些事件之間的時(shí)間很關(guān)鍵,則可以通過使用休眠和從休眠中醒來的通知來獲得實(shí)際事件發(fā)生之前的時(shí)間。
由于timer和其他重復(fù)事件是通過run loop觸發(fā)的, 越過run loop會(huì)打斷這些事件的傳遞機(jī)制。典型的例子是你通過進(jìn)入run loop并不斷重復(fù)向應(yīng)用請求run loop的事件從事從而實(shí)現(xiàn)了一個(gè)手勢跟蹤效果。因?yàn)槟愕拇a在主動(dòng)去直接抓取run loop事件,而不是讓應(yīng)用去自然地派發(fā)這些事件。 因?yàn)?,已?jīng)被激活的timer不會(huì)被觸發(fā),直到你抓取用戶手勢的事件結(jié)束,將對run loop的控制權(quán)交給系統(tǒng)后,才可以再次被觸發(fā)。
一個(gè)run loop 可以通過run loop 對象顯式喚醒。其他事件也可以喚醒run loop。例如:向run loop中添加一個(gè)非基于端口的輸入源會(huì)喚醒run loop以立刻處理這個(gè)輸入事件,而不是等其他事件發(fā)生。

什么時(shí)候需要使用Run Loop

只有需要為應(yīng)用開辟子線程的時(shí)候才需要顯式使用run loop。因?yàn)橹骶€程的run loop是重要的根基,所以app 框架會(huì)自動(dòng)創(chuàng)建主 run loop,并主動(dòng)開啟它。iOS中UIApplication框架(或者OS X中的NSApplication)的run 方法會(huì)將開啟應(yīng)用的main run loop作為啟動(dòng)的一個(gè)步驟。如果你是使用xocde模板去創(chuàng)建工程,你不需要顯式調(diào)用這個(gè)方法。
對于子線程來說,你需要考慮下run loop是否是必須的,如果是必要的,配置并手動(dòng)開啟它。不是所有的子線程都需要開啟run loop。例如,如果 使用子線程去執(zhí)行一些長時(shí)間運(yùn)行或者已經(jīng)定義好的任務(wù),你很大可能上可以避免開啟run loop。run loop適用于你想與線程有更多交互的場景。例如,在下面這些情況下,你很可能需要開啟一個(gè)run loop:

  • 使用端口和自定義輸入源與其他線程通信
  • 在線程中使用timer
  • 在cocoa應(yīng)用中使用任何performSelector...系列方法
  • 使線程重復(fù)周期性事件
    如果你決定使用run loop,那么run loop的適配和開啟是很簡單的。同其他多線程編程不同的是,你需要考慮好在適當(dāng)?shù)臅r(shí)機(jī)退出子線程。讓run loop自動(dòng)退出始終比強(qiáng)制終止它要好。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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