什么是RunLoop?
答:RunLoop是線程相關的基礎框架中的一部分,是一個事件處理對象,每一個線程都有與之對應的RunLoop,但并不是線程創建時就有RunLoop,只有當前線程第一次主動獲取RunLoop,系統才會創建當前線程相應的RunLoop。
RunLoop的作用是什么?
答:1).管理線程的生命周期及活動。 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ? ?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的操作都是線程安全的,但不建議跨線程處理。
如何啟動RunLoop?
答:可以通過NSRunLoop對象的run,runUntilDate:,runMode:beforeDate:方法,或通過CFRunLoopRef的CFRunLoopRun(),CFRunLoopRunInMode()來啟動RunLoop。
使用NSRunLoop和CFRunLoopRef來啟動RunLoop有何不同?
答:雖然NSRunLoop是基于CFRunLoopRef的高層組件,對NSRunLoop的操作最終都會轉換成對CFRunLoopRef的操作,但需要注意的是,NSRunLoop與CFRunLoopRef并不是簡單的接口轉換,就像啟動RunLoop一樣,NSRunLoop的Run方法與CFRunLoopRun()并不是對應著的,而且有著一定的區別。
如何退出RunLoop?
答:退出RunLoop的方式有三種,分別是:方式一.給RunLoop設定超時時間;方式二.使用CFRunLoopStop()函數顯式退出;方式三.移除CurrentMode的所有輸入源和定時源。
方式一是官法推薦的方式,可以安全有效地退出RunLoop。方式二,用CFRunLoopStop()函數并不是絕對可以退出Run的,要看以什么方式啟動RunLoop,如果用[[NSRunLoop currentRunLoop] run]來啟動RunLoop,那么用CFRunLoopStop()是無法退出RunLoop的,正如上面的偽代碼所示,以[[NSRunLoop currentRunLoop] run]啟動的RunLoop,唯一退出的方式是移除CurrentMode的所有輸入源和定時源,也就是方式三,但這種方式是不穩定的,因為系統會添加一些輸入源或定時源來完成一些操作。
RunLoop是否自動運行的?
答:所有線程的RunLoop都是默認不啟動的,但主線程的RunLoop會隨著應用的運行而被啟動。
RunLoop處理的輸入事件源有哪些?
答:RunLoop處理的事件源有兩種,分別是輸入源(Input Source)和定時源(Timer Source),而主線程的Main RunLoop還會處理GCD事件源。
什么是RunLoopMode?
答:RunLoop可以有多個RunLoopMode,RunLoopMode包含了輸入源(Input Source),定時源(Timer Source)和觀察者(Observer)。
RunLoop每次進入Run時都需要指定一個RunLoopMode,指定RunLoopMode后,只有當前RunLoopMode內的源和觀察者會被處理,其它的源和觀察者需要等到RunLoop運行其RunLoopMode時才會被處理,切換RunLoopMode的唯一方式是,退出當前RunLoopMode,重新指定一個RunLoopMode進入Run。當RunLoop選擇一個RunLoopMode進入Run時,若這個RunLoopMode中并沒有需要處理的源(輸入源或定時源),RunLoop就會直接退出。
RunLoopMode中的_timerPort是_timers中所有定時源的公共Port,_portToV1SourceMap記錄了_sources1以及對應的Port,通過Port獲取相應的Source1。
什么是CommonMode和CommonModeItem?
答:CommonModeItem是公共ModeItem,CommonMode是公共RunLoopMode,當把一個RunLoopMode注冊為CommonMode時,CommonModeItem被會自動添加到CommonMode里,當CommonModeItem有所改動時,CommonMode也會作出相應的改動。
什么是定時源(Timer Source)?
答:用于延時或重復的時間間隔處理事件。
如何使用定時源?
答:CFRunLoopTimerRef是RunLoop中唯一的定時源,以定時器的形式表示和使用,可選擇的定時器有,NSTimer,CADisplayLink,CFRunLoopTimerRef,GCD Timer。
四種定時器的區別是什么?
答:NSTimer和CADisplayLink是Cocoa提供的高層定時器,CFRunLoopTimerRef是Core Foundation提供的基礎定時器,NSTimer和CADisplayLink是建立在CFRunLoopTimerRef之上的高層組件,而CFRunLoopTimerRef是建立在mk_timer之上。NSTimer和CADisplayLink主要區別在于信號的發射頻率不同,CADisplayLink的信號發射頻率固定在16.67ms一次,而NSTimer的信號發射頻率可自由定義(具體請看iOS10定時消息的改動)。
GCD Timer有別于前三種定時器,是由GCD系統所管理的定時器,通過一定的時間間隔dispatch任務到相應的隊列中處理,以主線程為例,時間間隔到達后,GCD系統將Block dispatch到主線程對應的Main Queue,等待Main RunLoop檢測和處理。
如何選擇使用哪一種定時器?
答:除非需要實現定時動畫,否則不建議使用CADisplayLink作為定時器(具體請看iOS10定時消息的改動。什么是定時動畫?請查看iOS動畫的基礎知識);NSTimer適用于大部份情況,但需要注意循環引用的問題;GCD Timer的缺點在于,不能在自己所創建的子線程中使用。
CFRunLoopTimerRef的觸發原理是怎樣的?
答:具體請看iOS10定時消息的改動。
什么是輸入源?
答:是RunLoop所處理的事件源之一,主要用于線程或進程交互,輸入源分為基于端口輸入源(Source1)和非端口輸入源(Source0)。
基于端口輸入源(Source1)與非端口輸入源(Source0)的區別是什么?
答:1).Source0與Source1都是CFRunLoopSourceRef類型,但配置方式不同,Source0用CFRunLoopSourceContext來配置,Source1用CFRunLoopSourceContext1來配置。
? ? ?2).Source0與Source1都可用于線程(或進程)交互,但交互的形式有所不同,Source1監聽端口,當端口有消息到達時,相應的Source1就會被觸發回調,完成相應的操作;而Source0并不監聽端口,讓Source0執行回調需要手動標記Source0為待處理狀態,還需要呼醒Source0所在的RunLoop。
? ? ? 3).從Source0與Source1的交式方式了解到,Source1的交互會主動呼醒所在的RunLoop,而Source0的交互則需要依賴其它線程來呼醒Source0所在的RunLoop。
? ? ? 4).一次Loop只能執行一個Source1的回調,但一次Loop可以執行多個待處理的Source0的回調。
如何創建Source0?
如何標記Source為待處理狀態,且呼醒所在的RunLoop?
如何創建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接收和發送需要是同一個對象;CFMessagePortRef接收和發送的Port所用的name要相同,CFMessagePortSendRequest()函數通過CFMessagePortRef的name來查找相應的接收端口來進行消息發送(不建議直接使用mach_msg()來發送消息,關于Port可以查看Inter-Process Communication)。
RunLoop的內部邏輯是怎樣的?
需要注意的是第五步,官方文檔寫的是如果有基于端口的輸入源待處理,就進入第九步,這跟CFRunLoop的源碼不同。
從源碼可以看到,第五步檢測的是GCD端口事件,而不是官方文檔所寫的基于端口的輸入源,但經過大量測試發現,第五步實際上會檢測所有未處理的端口事件,而并非像官方文檔或源碼所展示的那樣(太坑爹了,居然官方文檔跟源碼不同,源碼又和實際測試結果不同??)。
如果有什么地方寫錯的麻煩指出,如果有什么還想知道的請在評論留言,我會盡快補上的。