1、什么是Runloop
Runloop是通過內部維護的事件循環來對事件/消息進行管理的一個對象。事件循環不是while死循環,而是狀態轉換,即用戶態-內核態的轉換。
2、RunLoop事件循環
下圖是仿照Runloop內部邏輯實現的偽代碼:
根據以上代碼我們可以看出,RunLoop主要處理以下6類事件:
1.Observer事件:runloop中狀態變化時進行通知。(微信卡頓監控就是利用這個事件通知來記錄下最近一次main runloop活動時間,在另一個check線程中用定時器檢測當前時間距離最后一次活動時間過久來判斷在主線程中的處理邏輯耗時和卡主線程)。
2.Block事件:非延遲的performSelector,非延遲的dispatch_after,block回調。
3.Main_Dispatch_Queue事件:GCD中dispatch到main queue的block會被dispatch到main loop執行。
4.Timer事件:延遲的performSelector,延遲的dispatch_after,timer事件等。
5.Source0事件:非基于Port的處理事件,不能主動喚醒休眠中的RunLoop,需要手動觸發。
6.Source1事件:基于mach_Port的,來自系統內核或者其他進程或線程的事件,可以主動喚醒休眠中的RunLoop(推測CADisplayLink也是這里觸發)。
簡化的流程圖如下:
在每次運行開啟RunLoop的時候,所在線程的RunLoop會自動處理之前未處理的事件,并且通知相關的觀察者。具體的順序如下:
通知觀察者RunLoop已經啟動
通知觀察者即將要開始的定時器
通知觀察者任何即將啟動的非基于端口的源
啟動任何準備好的非基于端口的源
如果基于端口的源準備好并處于等待狀態,立即啟動,并進入步驟9
通知觀察者線程進入休眠狀態
-
將線程置于休眠直到任一下面的事件發生:
某一事件到達基于端口的源
定時器啟動
RunLoop設置的時間已經超時
RunLoop被顯式喚醒
通知觀察者線程將被喚醒
-
處理未處理的事件
如果用戶定義的定時器啟動,處理定時器事件并重啟RunLoop,進入步驟2
如果輸入源啟動,傳遞相應的消息
如果RunLoop被顯示喚醒而且時間還沒超時,重啟RunLoop。進入步驟2
通知觀察者RunLoop結束。
3、RunLoop和線程的關系
RunLoop 和線程是息息相關的,我們知道線程的作用是用來執行特定的一個或多個任務,在默認情況下,線程執行完之后就會退出,就不能再執行任務了。這時我們就需要采用一種方式來讓線程能夠不斷地處理任務,并不退出。所以,我們就有了 RunLoop。
1、一條線程對應一個RunLoop對象,每條線程都有唯一一個與之對應的 RunLoop 對象。
2、RunLoop 并不保證線程安全。我們只能在當前線程內部操作當前線程的 RunLoop 對象,而不能在當前線程內部去操作其他線程的 RunLoop 對象方法。
3、RunLoop 對象在第一次獲取 RunLoop 時創建,銷毀則是在線程結束的時候。
4、主線程的 RunLoop 對象系統自動幫助我們創建好了,而子線程的 RunLoop對象需要我們主動創建和維護。
4、RunLoop與硬件事件的關系
當一個硬件事件(觸摸/鎖屏/搖晃/加速等)發生后,首先由 IOKit.framework 生成一個 IOHIDEvent 事件并由 SpringBoard 接收, 隨后由mach port 轉發給需要的App進程。
蘋果注冊了一個 Source1 (基于 mach port 的) 來接收系統事件,通過回調函數觸發Source0(所以UIEvent實際上是基于Source0的),調用 _UIApplicationHandleEventQueue()
進行應用內部的分發。_UIApplicationHandleEventQueue()
會把 IOHIDEvent 處理并包裝成 UIEvent 進行處理或分發,其中包括識別 UIGesture/處理屏幕旋轉/發送給 UIWindow 等。
如果上一步的_UIApplicationHandleEventQueue()
識別到是一個guesture手勢,會調用Cancel方法將當前的touchesBegin/Move/End 系列回調打斷。隨后系統將對應的 UIGestureRecognizer 標記為待處理。蘋果注冊了一個 Observer 監測 BeforeWaiting (Loop即將進入休眠) 事件,其回調函數為 _UIGestureRecognizerUpdateObserver()
,其內部會獲取所有剛被標記為待處理的 GestureRecognizer,并執行GestureRecognizer的回調。當有 UIGestureRecognizer 的變化(創建/銷毀/狀態改變)時,這個回調都會進行相應處理。
5、RunLoop與Core Animation渲染的關系
iOS 圖形服務接收到 VSync 信號后,會通過 IPC 通知到 App 內。App 的 Runloop 在啟動后會注冊對應的 CFRunLoopSource 通過 mach_port 接收傳過來的時鐘信號通知,隨后 Source 的回調會驅動整個 App 的動畫與顯示。Core Animation 在 RunLoop 中注冊了一個 Observer,監聽了 BeforeWaiting 和 Exit 事件。這個 Observer 的優先級是 2000000,低于常見的其他 Observer。
當UI改變( Frame變化、 UIView/CALayer 的層級結構變化等)時,或手動調用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay
方法后,這些操作最終都會被 CALayer 捕獲,并通過 CATransaction 提交到一個中間狀態去。
當RunLoop 即將進入休眠(或者退出)時,關注該事件的 Observer 都會得到通知。這時 CA 注冊的那個 Observer 就會在回調中,把所有的中間狀態合并提交到 GPU 去顯示;如果此處有動畫,CA 會通過 DisplayLink 等機制多次觸發相關流程。
6、RunLoop與AutoReleasePool的關系
在iOS應用啟動后會注冊兩個Observer管理和維護AutoreleasePool。在應用程序剛剛啟動時打印currentRunLoop可以看到系統默認注冊了很多個Observer,其中有兩個Observer的callout都是_ wrapRunLoopWithAutoreleasePoolHandler
,這兩個是和自動釋放池相關的兩個監聽。
第一個Observer會監聽RunLoop的進入,它會回調objc_autoreleasePoolPush()
向當前的AutoreleasePoolPage增加一個哨兵對象標志創建自動釋放池。這個Observer的order是-2147483647優先級最高,確保發生在所有回調操作之前。
第二個Observer會監聽RunLoop的進入休眠和即將退出RunLoop兩種狀態,在即將進入休眠時會調用objc_autoreleasePoolPop()
和 objc_autoreleasePoolPush()
根據情況從最新加入的對象一直往前清理直到遇到哨兵對象。而在即將退出RunLoop時會調用objc_autoreleasePoolPop()
釋放自動自動釋放池內對象。這個Observer的order是2147483647,優先級最低,確保發生在所有回調操作之后。
主線程的其他操作通常均在這個AutoreleasePool之內(main函數中),以盡可能減少內存維護操作。當然你如果需要顯式釋放(例如循環)時可以自己創建AutoreleasePool,否則一般不需要自己創建。