深入理解runloop

線程中的runloop

在開發(fā)中,我們會(huì)經(jīng)常接觸到線程,比如在主線程中更新UI,在子線程中異步請求等,而線程中最重要的一個(gè)組成部分便是runloop,其是用來管理線程的。runloop在線程中有以下兩個(gè)作用:

  1. 保證線程不退出
  2. 監(jiān)聽并處理事件,使得線程有事件時(shí)工作,無事件時(shí)睡眠

每個(gè)線程中都有一個(gè)runloop,主線程中的runloop是在應(yīng)用程序啟動(dòng)時(shí)創(chuàng)建的,默認(rèn)是開啟的,子線程中的runloop采用的是延遲加載的方式,如果我們不主動(dòng)獲取,子線程的runloop是不會(huì)創(chuàng)建的。

解析runloop

runloop三要素: Modes(模式)、sources(源)、Observers(觀察者)。當(dāng)有事件源觸發(fā)時(shí),runloop會(huì)被喚醒并處理事件源的方法,并通知該runloop運(yùn)行模式下的所有觀察者。

Run Loop Modes(運(yùn)行循環(huán)模式)

運(yùn)行循環(huán)在工作時(shí),會(huì)有多種運(yùn)行模式。我們常用到的模式有:NSDefaultRunLoopMode和NSRunLoopCommonModes 。每個(gè)RunLoop Mode都可以看作是一個(gè)集合,其中包含了其監(jiān)聽的事件源以及在事件發(fā)生時(shí)需要通知的RunLoop Observers。在相應(yīng)的模式下,只有對應(yīng)于該模式的source會(huì)被處理,同樣也只有對應(yīng)于該模式的observer會(huì)被通知。其實(shí)運(yùn)行循環(huán)模式起到一個(gè)過濾作用,可以過濾到我們不關(guān)心的其他的source事件。

Cocoa 和 Core Foundation框架定義了一個(gè)默認(rèn)的和一些常用的運(yùn)行循環(huán)模式,如下表:

Mode Name Description
Default NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation) 默認(rèn)模式,一般設(shè)置為此模式.
Connection NSConnectionReplyMode (Cocoa) Cocoa用該模式來監(jiān)聽NSConnection請求的回復(fù),該模式為系統(tǒng)使用,我們一般不會(huì)用到
Modal NSModalPanelRunLoopMode (Cocoa) Cocoa 用此模式來區(qū)分 modal panels事件.
Event tracking NSEventTrackingRunLoopMode (Cocoa) Cocoa 在該模式下,限制其他source的事件
Common modes NSRunLoopCommonModes (Cocoa)kCFRunLoopCommonModes (Core Foundation) 這是一個(gè)可配置的模式組,在該模式下的source 事件,和模式組里的其他模式都會(huì)產(chǎn)生聯(lián)系。在Cocoa框架中,該模式默認(rèn)包含Default、Modal和 Event tracking 三種模式,在Core Foundation中默認(rèn)只包含Default模式,我們可以用CFRunLoopAddCommonMode 函數(shù)來添加自定義的模式。
Run Loop Sources

先來看runloop和sources的結(jié)構(gòu)圖

runloop.png

runloop可以監(jiān)聽并處理事件,其監(jiān)聽的事件源有兩種類型:Input sources(輸入源) 和** Timer sources(定時(shí)器源)**。輸入源異步傳遞事件到線程中,通常消息來自其它線程或不同的應(yīng)用,定時(shí)器源同步傳遞事件。

  • Input sources
    輸入源異步發(fā)送事件到線程中,輸入源共有兩種類型:一種是基于端口的輸入源(Port-based input sources),一種是自定義輸入源(Custom input sources)?;诙丝诘臄?shù)據(jù)源監(jiān)聽?wèi)?yīng)用的Mach端口,自定義輸入源監(jiān)聽自定義的事件源,二者的唯一區(qū)別是:基于端口的輸入源的信號(hào)是由內(nèi)核自動(dòng)觸發(fā)的,自定義輸入源的信號(hào)是由其它線程手動(dòng)觸發(fā)的。
  • Port-Based Sources
    Cocoa 和 Core Foundation框架給我們提供了一些創(chuàng)建基于端口的源的對象和函數(shù)。
    在 Cocoa中,我們只需要?jiǎng)?chuàng)建一個(gè) NSPort對象,并將其添加到運(yùn)行循環(huán)中就可以了,NSPort對象會(huì)自己創(chuàng)建輸入源。

     NSPort *port = [NSPort port];
     [port scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    

    但是在Core Foundation中,我們就需要手動(dòng)創(chuàng)建端口和輸入源,我們可以用CFMachPortRef, CFMessagePortRef, or CFSocketRef來創(chuàng)建相應(yīng)的對象

  • Custom input sources
    我們也可以自定義輸入源,自定義輸入源時(shí),我們需要配置定義輸入源的行為,運(yùn)行循環(huán)模式,輸入源事件的傳遞機(jī)制,以及銷毀輸入源等。

  • Cocoa Perform Selector Sources
    除了基于端口的源,Cocoa也定義了自定義輸入源--Perform Selector Sources,這使得我們可以在任意線程中執(zhí)行我們的方法,Perform Selector 的方法在目標(biāo)線程中是串行執(zhí)行的,這樣就解決了多個(gè)方法在一個(gè)線程中執(zhí)行的同步問題,和基于端口的源不同,Perform Selector 在方法執(zhí)行完成之后,會(huì)將自己從runloop中移除。

Note: 當(dāng)我們在目標(biāo)線程上執(zhí)行我們的方法時(shí),目標(biāo)線程的運(yùn)行循環(huán)必須開啟,否則方法不會(huì)執(zhí)行。主線程的運(yùn)行循環(huán)默認(rèn)開啟,子線程的運(yùn)行循環(huán)默認(rèn)不開啟,因此我們想要在子線程上運(yùn)行我們的程序,需要手動(dòng)開啟子線程的運(yùn)行循環(huán)。

  • Timer Sources(定時(shí)器源)
    定時(shí)器源發(fā)送同步事件到我們的線程中,對于線程來說,定時(shí)器是一種通知自己做一些事情的方式。雖然,定時(shí)器源會(huì)發(fā)出基于時(shí)間的通知,但是定時(shí)器并不是實(shí)時(shí)機(jī)制,跟輸入源一樣,定時(shí)器源也跟runloop的運(yùn)行模式相關(guān),如果定時(shí)器的模式,不在運(yùn)行循環(huán)當(dāng)前監(jiān)控的模式中,定時(shí)器方法是不會(huì)執(zhí)行的。另外如果定時(shí)器觸發(fā)時(shí),運(yùn)行循環(huán)正在執(zhí)行操作,定時(shí)器將會(huì)在下次運(yùn)行循環(huán)調(diào)用時(shí)觸發(fā)。
Run Loop Observers

運(yùn)行循環(huán)在運(yùn)行時(shí),會(huì)發(fā)出一些通知,我們可以通過注冊O(shè)bservers來監(jiān)聽運(yùn)行循環(huán)當(dāng)前所處的狀態(tài),運(yùn)行循環(huán)狀態(tài)有以下幾種:

  • 即將進(jìn)入運(yùn)行循環(huán)
  • 運(yùn)行循環(huán)將要處理定時(shí)器
  • 運(yùn)行循環(huán)將要處理輸入源
  • 運(yùn)行循環(huán)將要進(jìn)入睡眠
  • 運(yùn)行循環(huán)被喚醒,但是還未處理事件
  • 運(yùn)行循環(huán)退出
The Run Loop Sequence of Events(運(yùn)行循環(huán)執(zhí)行事件的順序)
  1. 通知觀察者即將進(jìn)入運(yùn)行循環(huán)
  2. 通知觀察者將要處理定時(shí)器事件
  3. 通知觀察者將要處理輸入源(基于端口的源除外)事件
  4. 處理輸入源(基于端口的源除外)事件
  5. 如果有基于端口的輸入源事件,則立即處理。然后跳到第9步。
  6. 通知觀察者線程將要進(jìn)入休眠
  7. 線程進(jìn)入休眠,等待被喚醒,下列事件可以喚醒線程
  • 基于端口的源的事件
  • 定時(shí)器事件
  • 設(shè)置的runloop超時(shí)時(shí)間到期
  • runloop主動(dòng)被喚醒
  1. 通知觀察者線程剛被喚醒
  2. 處理掛起的事件
  • 如果有用戶定義的定時(shí)器事件觸發(fā),處理定時(shí)器事件,并且重啟運(yùn)行循環(huán),進(jìn)入第2步
  • 如果觸發(fā)的是輸入源事件,傳送事件
  • 如果runloop被主動(dòng)喚醒,并且沒有超時(shí),重啟runloop,進(jìn)入第2步.
  1. 通知觀察者運(yùn)行循環(huán)將要退出

代碼示例后續(xù)會(huì)整理分享

最后編輯于
?著作權(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)容