什么是RunLoop?
答:RunLoop是線程相關(guān)的基礎(chǔ)框架中的一部分,是一個(gè)事件處理對象,每一個(gè)線程都有與之對應(yīng)的RunLoop,但并不是線程創(chuàng)建時(shí)就有RunLoop,只有當(dāng)前線程第一次主動(dòng)獲取RunLoop,系統(tǒng)才會(huì)創(chuàng)建當(dāng)前線程相應(yīng)的RunLoop。
RunLoop的作用是什么?
答:1).管理線程的生命周期及活動(dòng)。
2).處理輸入事件源以及通知觀察者。
如何使用RunLoop?
答:iOS/OSX提供了Core Foundation(CFRunLoopRef)和Cocoa(NSRunLoop)兩套API來使用RunLoop,可以通過CFRunLoopGetMain() 和 CFRunLoopGetCurrent()或[NSRunLoop mainRunLoop]和[NSRunLoop currentRunLoop]來獲取RunLoop對象,NSRunLoop是基于CFRunLoopRef的高層組件,CFRunLoopRef的API都是線程安全的,但NSRunLoop的API不是線程安全的。雖然CFRunLoopRef的操作都是線程安全的,但不建議跨線程處理。
如何啟動(dòng)RunLoop?
答:可以通過NSRunLoop對象的run,runUntilDate:,runMode:beforeDate:方法,或通過CFRunLoopRef的CFRunLoopRun(),CFRunLoopRunInMode()來啟動(dòng)RunLoop。
使用NSRunLoop和CFRunLoopRef來啟動(dòng)RunLoop有何不同?
答:雖然NSRunLoop是基于CFRunLoopRef的高層組件,對NSRunLoop的操作最終都會(huì)轉(zhuǎn)換成對CFRunLoopRef的操作,但需要注意的是,NSRunLoop與CFRunLoopRef并不是簡單的接口轉(zhuǎn)換,就像啟動(dòng)RunLoop一樣,NSRunLoop的Run方法與CFRunLoopRun()并不是對應(yīng)著的,而且有著一定的區(qū)別。


如何退出RunLoop?
答:退出RunLoop的方式有三種,分別是:方式一.給RunLoop設(shè)定超時(shí)時(shí)間;方式二.使用CFRunLoopStop()函數(shù)顯式退出;方式三.移除CurrentMode的所有輸入源和定時(shí)源。
方式一是官法推薦的方式,可以安全有效地退出RunLoop。方式二,用CFRunLoopStop()函數(shù)并不是絕對可以退出Run的,要看以什么方式啟動(dòng)RunLoop,如果用[[NSRunLoop currentRunLoop] run]來啟動(dòng)RunLoop,那么用CFRunLoopStop()是無法退出RunLoop的,正如上面的偽代碼所示,以[[NSRunLoop currentRunLoop] run]啟動(dòng)的RunLoop,唯一退出的方式是移除CurrentMode的所有輸入源和定時(shí)源,也就是方式三,但這種方式是不穩(wěn)定的,因?yàn)橄到y(tǒng)會(huì)添加一些輸入源或定時(shí)源來完成一些操作。
RunLoop是否自動(dòng)運(yùn)行的?
答:所有線程的RunLoop都是默認(rèn)不啟動(dòng)的,但主線程的RunLoop會(huì)隨著應(yīng)用的運(yùn)行而被啟動(dòng)。
RunLoop處理的輸入事件源有哪些?
答:RunLoop處理的事件源有兩種,分別是輸入源(Input Source)和定時(shí)源(Timer Source),而主線程的Main RunLoop還會(huì)處理GCD事件源。
什么是RunLoopMode?
答:RunLoop可以有多個(gè)RunLoopMode,RunLoopMode包含了輸入源(Input Source),定時(shí)源(Timer Source)和觀察者(Observer)。

RunLoop每次進(jìn)入Run時(shí)都需要指定一個(gè)RunLoopMode,指定RunLoopMode后,只有當(dāng)前RunLoopMode內(nèi)的源和觀察者會(huì)被處理,其它的源和觀察者需要等到RunLoop運(yùn)行其RunLoopMode時(shí)才會(huì)被處理,切換RunLoopMode的唯一方式是,退出當(dāng)前RunLoopMode,重新指定一個(gè)RunLoopMode進(jìn)入Run。當(dāng)RunLoop選擇一個(gè)RunLoopMode進(jìn)入Run時(shí),若這個(gè)RunLoopMode中并沒有需要處理的源(輸入源或定時(shí)源),RunLoop就會(huì)直接退出。

RunLoopMode中的_timerPort是_timers中所有定時(shí)源的公共Port,_portToV1SourceMap記錄了_sources1以及對應(yīng)的Port,通過Port獲取相應(yīng)的Source1。

什么是CommonMode和CommonModeItem?
答:CommonModeItem是公共ModeItem,CommonMode是公共RunLoopMode,當(dāng)把一個(gè)RunLoopMode注冊為CommonMode時(shí),CommonModeItem被會(huì)自動(dòng)添加到CommonMode里,當(dāng)CommonModeItem有所改動(dòng)時(shí),CommonMode也會(huì)作出相應(yīng)的改動(dòng)。
什么是定時(shí)源(Timer Source)?
答:用于延時(shí)或重復(fù)的時(shí)間間隔處理事件。
如何使用定時(shí)源?
答:CFRunLoopTimerRef是RunLoop中唯一的定時(shí)源,以定時(shí)器的形式表示和使用,可選擇的定時(shí)器有,NSTimer,CADisplayLink,CFRunLoopTimerRef,GCD Timer。
四種定時(shí)器的區(qū)別是什么?
答:NSTimer和CADisplayLink是Cocoa提供的高層定時(shí)器,CFRunLoopTimerRef是Core Foundation提供的基礎(chǔ)定時(shí)器,NSTimer和CADisplayLink是建立在CFRunLoopTimerRef之上的高層組件,而CFRunLoopTimerRef是建立在mk_timer之上。NSTimer和CADisplayLink主要區(qū)別在于信號的發(fā)射頻率不同,CADisplayLink的信號發(fā)射頻率固定在16.67ms一次,而NSTimer的信號發(fā)射頻率可自由定義(具體請看iOS10定時(shí)消息的改動(dòng))。

GCD Timer有別于前三種定時(shí)器,是由GCD系統(tǒng)所管理的定時(shí)器,通過一定的時(shí)間間隔dispatch任務(wù)到相應(yīng)的隊(duì)列中處理,以主線程為例,時(shí)間間隔到達(dá)后,GCD系統(tǒng)將Block dispatch到主線程對應(yīng)的Main Queue,等待Main RunLoop檢測和處理。
如何選擇使用哪一種定時(shí)器?
答:除非需要實(shí)現(xiàn)定時(shí)動(dòng)畫,否則不建議使用CADisplayLink作為定時(shí)器(具體請看iOS10定時(shí)消息的改動(dòng)。什么是定時(shí)動(dòng)畫?請查看iOS動(dòng)畫的基礎(chǔ)知識);NSTimer適用于大部份情況,但需要注意循環(huán)引用的問題;GCD Timer的缺點(diǎn)在于,不能在自己所創(chuàng)建的子線程中使用。
CFRunLoopTimerRef的觸發(fā)原理是怎樣的?
答:具體請看iOS10定時(shí)消息的改動(dòng)。
什么是輸入源?
答:是RunLoop所處理的事件源之一,主要用于線程或進(jìn)程交互,輸入源分為基于端口輸入源(Source1)和非端口輸入源(Source0)。
基于端口輸入源(Source1)與非端口輸入源(Source0)的區(qū)別是什么?
答:1).Source0與Source1都是CFRunLoopSourceRef類型,但配置方式不同,Source0用CFRunLoopSourceContext來配置,Source1用CFRunLoopSourceContext1來配置。
2).Source0與Source1都可用于線程(或進(jìn)程)交互,但交互的形式有所不同,Source1監(jiān)聽端口,當(dāng)端口有消息到達(dá)時(shí),相應(yīng)的Source1就會(huì)被觸發(fā)回調(diào),完成相應(yīng)的操作;而Source0并不監(jiān)聽端口,讓Source0執(zhí)行回調(diào)需要手動(dòng)標(biāo)記Source0為待處理狀態(tài),還需要呼醒Source0所在的RunLoop。


3).從Source0與Source1的交式方式了解到,Source1的交互會(huì)主動(dòng)呼醒所在的RunLoop,而Source0的交互則需要依賴其它線程來呼醒Source0所在的RunLoop。
4).一次Loop只能執(zhí)行一個(gè)Source1的回調(diào),但一次Loop可以執(zhí)行多個(gè)待處理的Source0的回調(diào)。
如何創(chuàng)建Source0?

如何標(biāo)記Source為待處理狀態(tài),且呼醒所在的RunLoop?

如何創(chuàng)建Source1以及如何交互?
答:有Cocoa和Core Foundation兩套API來配置和使用Source1;Cocoa有NSPort,NSMachPort,NSMessagePort,NSSocketPort等類,Core Foundation有CFMachPortRef,CFMessagePortRef,CFSocketRef等。其中用得比較多的是NSMachPort和CFMessagePortRef。


Cocoa所提供的類只是建立在Core Foundation之上的高層組件,且提供了toll-free bridged。需要注意的是,NSMachPort接收和發(fā)送需要是同一個(gè)對象;CFMessagePortRef接收和發(fā)送的Port所用的name要相同,CFMessagePortSendRequest()函數(shù)通過CFMessagePortRef的name來查找相應(yīng)的接收端口來進(jìn)行消息發(fā)送(不建議直接使用mach_msg()來發(fā)送消息,關(guān)于Port可以查看Inter-Process Communication)。
RunLoop的內(nèi)部邏輯是怎樣的?


需要注意的是第五步,官方文檔寫的是如果有基于端口的輸入源待處理,就進(jìn)入第九步,這跟CFRunLoop的源碼不同。

從源碼可以看到,第五步檢測的是GCD端口事件,而不是官方文檔所寫的基于端口的輸入源,但經(jīng)過大量測試發(fā)現(xiàn),第五步實(shí)際上會(huì)檢測所有未處理的端口事件,而并非像官方文檔或源碼所展示的那樣(太坑爹了,居然官方文檔跟源碼不同,源碼又和實(shí)際測試結(jié)果不同??)。
如果有什么地方寫錯(cuò)的麻煩指出,如果有什么還想知道的請?jiān)谠u論留言,我會(huì)盡快補(bǔ)上的。