線程中的runloop
在開發(fā)中,我們會(huì)經(jīng)常接觸到線程,比如在主線程中更新UI,在子線程中異步請求等,而線程中最重要的一個(gè)組成部分便是runloop,其是用來管理線程的。runloop在線程中有以下兩個(gè)作用:
- 保證線程不退出
- 監(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可以監(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í)行事件的順序)
- 通知觀察者即將進(jìn)入運(yùn)行循環(huán)
- 通知觀察者將要處理定時(shí)器事件
- 通知觀察者將要處理輸入源(基于端口的源除外)事件
- 處理輸入源(基于端口的源除外)事件
- 如果有基于端口的輸入源事件,則立即處理。然后跳到第9步。
- 通知觀察者線程將要進(jìn)入休眠
- 線程進(jìn)入休眠,等待被喚醒,下列事件可以喚醒線程
- 基于端口的源的事件
- 定時(shí)器事件
- 設(shè)置的runloop超時(shí)時(shí)間到期
- runloop主動(dòng)被喚醒
- 通知觀察者線程剛被喚醒
- 處理掛起的事件
- 如果有用戶定義的定時(shí)器事件觸發(fā),處理定時(shí)器事件,并且重啟運(yùn)行循環(huán),進(jìn)入第2步
- 如果觸發(fā)的是輸入源事件,傳送事件
- 如果runloop被主動(dòng)喚醒,并且沒有超時(shí),重啟runloop,進(jìn)入第2步.
- 通知觀察者運(yùn)行循環(huán)將要退出
代碼示例后續(xù)會(huì)整理分享