作者:LuckyRoc
連結:https://juejin.im/post/5c36e369f265da61682b92bb
Runloop
Runloop是事件接收和分發機制的一個實現。是執行緒相關的基礎框架的一部分。一個Runloop就是一個事件處理的迴圈,用來不停的排程工作及處理輸入事件。使用runloop的目的就是讓你的執行緒
RunLoop的主要目的
保證程式執行的執行緒不會被系統終止,如果沒有RunLoop,UIApplicationMain函式執行完畢之後將直接傳回,就是說程式一啟動然後就結束,在有工作的時候忙於工作,而沒有工作的時候處於休眠狀態,
什麼時候使用Runloop
當需要和該執行緒進行互動的時候才會使用Runloop
Runloop Mode
一個Runloop可能有幾個mode
Runloop Mode 實際上是 Source,Timer 和 Observer 的集合,不同的 Mode 把不同組的 Source,Timer 和 Observer 隔絕開來。Runloop 在某個時刻只能跑在一個 Mode 下,處理這一個 Mode 當中的 Source,Timer 和 Observer。
蘋果檔案中提到的 Mode 有五個,分別是:
-
NSDefaultRunLoopMode:預設的mode,正常情況下都是在這個mode
-
NSConnectionReplyMode
-
NSModalPanelRunLoopMode
-
NSEventTrackingRunLoopMode:使用這個Mode去跟蹤來自使用者互動的事件(比如UITableView上下滑動)
-
NSRunLoopCommonModes
iOS 中公開暴露出來的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes。 NSRunLoopCommonModes 實際上是一個 Mode 的集合,預設包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode。
Source
即可以喚醒Runloop的一些事件。比如使用者點選了螢幕,就會建立一個input source。
-
source0 : 非系統事件
只包含了一個回呼(函式指標),它並不能主動觸發事件。使用時,你需要先呼叫 CFRunLoopSourceSignal(source),將這個 Source 標記為待處理,然後手動呼叫 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。
-
source1 : 系統事件
包含了一個 mach_port和一個回呼(函式指標),被用於透過內核和其他執行緒相互傳送訊息。這種 Source 能主動喚醒 RunLoop 的執行緒
Timer
我們經常用的NSTimer就屬於這一類。
Observer
某個observer可以監聽runloop的狀態變化,並作出一定反應。
RunLoop執行流程
經典大圖
沒有事情的時候,Runloop處於休眠狀態。當外部source將其喚醒後,它會依次處理接收到的timer/source,然後再次進入休眠。
常見的面試題:
Runloop和執行緒是什麼關係?
每條執行緒都有唯一的一個與之對應的RunLoop物件,其關係是儲存在一個全域性的 Dictionary 裡;主執行緒的RunLoop已經自動建立,子執行緒的RunLoop需要主動建立;RunLoop在第一次獲取時建立,在執行緒結束時銷毀
Runloop的mode作用是什麼?
指定事件在執行迴圈中的優先順序的,
執行緒的執行需要不同的樣式,去響應各種不同的事件,去處理不同情境樣式。(比如可以最佳化tableview的時候可以設定UITrackingRunLoopMode下不進行一些操作,比如設定圖片等。)
以+scheduledTimerWithTimeInterval:的方式觸發的timer,在滑動頁面上的串列時,timer會暫停回呼, 為什麼?
滑動scrollView時,主執行緒的RunLoop會切換到UITrackingRunLoopMode這個Mode,執行的也是UITrackingRunLoopMode下的任務(Mode中的item),而timer是新增在NSDefaultRunLoopMode下的,所以timer任務並不會執行,只有當UITrackingRunLoopMode的任務執行完畢,runloop切換到NSDefaultRunLoopMode後,才會繼續執行timer。
如何解決在滑動頁面上的串列時,timer會暫停回呼?
將Timer放到NSRunLoopCommonModes中執行即可
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
NSTImer使用時需要註意什麼?
-
註意timer新增到runloop時應該設定為什麼mode
-
註意timer在不需要時,一定要呼叫invalidate方法使定時器失效,否則得不到釋放
RunLoop 有哪些應用?
常駐記憶體、AutoreleasePool 自動釋放池
AutoreleasePool 和 RunLoop 有什麼聯絡?
iOS應用啟動後會註冊兩個 Observer 管理和維護 AutoreleasePool。應用程式剛剛啟動時預設註冊了很多個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 ,優先順序最低,確保發生在所有回呼操作之後。
NSRunLoop 和 CFRunLoopRef 區別
CFRunLoopRef 基於C 執行緒安全,NSRunLoop 基於 CFRunLoopRef 面向物件的API 是不安全的
備註
由於作者水平有限,難免出現紕漏,如有問題還請不吝賜教。