【iOS】RunLoop底層詳解

1、什么是Runloop

Runloop是通過內部維護的事件循環來對事件/消息進行管理的一個對象。事件循環不是while死循環,而是狀態轉換,即用戶態-內核態的轉換。

image.png

2、RunLoop事件循環

下圖是仿照Runloop內部邏輯實現的偽代碼:

image.png

根據以上代碼我們可以看出,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也是這里觸發)。

簡化的流程圖如下:

1877784-94c6cdb3a7864593.png

在每次運行開啟RunLoop的時候,所在線程的RunLoop會自動處理之前未處理的事件,并且通知相關的觀察者。具體的順序如下:

  1. 通知觀察者RunLoop已經啟動

  2. 通知觀察者即將要開始的定時器

  3. 通知觀察者任何即將啟動的非基于端口的源

  4. 啟動任何準備好的非基于端口的源

  5. 如果基于端口的源準備好并處于等待狀態,立即啟動,并進入步驟9

  6. 通知觀察者線程進入休眠狀態

  7. 將線程置于休眠直到任一下面的事件發生:

    • 某一事件到達基于端口的源

    • 定時器啟動

    • RunLoop設置的時間已經超時

    • RunLoop被顯式喚醒

  8. 通知觀察者線程將被喚醒

  9. 處理未處理的事件

    • 如果用戶定義的定時器啟動,處理定時器事件并重啟RunLoop,進入步驟2

    • 如果輸入源啟動,傳遞相應的消息

    • 如果RunLoop被顯示喚醒而且時間還沒超時,重啟RunLoop。進入步驟2

  10. 通知觀察者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,否則一般不需要自己創建。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容

  • 一 什么是Runloop 二 Runloop的運行邏輯 三 Runloop在實際開發中的應用 一 什么是Runlo...
    當前明月閱讀 199評論 0 3
  • 前言 最近離職了,可以盡情熬夜寫點總結,不用擔心第二天上班爽并蛋疼著,這篇的主角 RunLoop 一座大山,涵蓋的...
    zerocc2014閱讀 12,384評論 13 67
  • RunLoop源碼剖析---圖解RunLoop 源碼面前,了無秘密 前言 我們在iOS APP中的main函數如下...
    薩繆閱讀 1,467評論 0 7
  • 1、RunLoop初探 1.1、RunLoop是什么? RunLoop從字面上來說是跑圈的意思,如果這樣理解不免有...
    風緊扯呼閱讀 728評論 0 5
  • Runloop 是和線程緊密相關的一個基礎組件,是很多線程有關功能的幕后功臣。盡管在平常使用中幾乎不太會直接用到,...
    jackyshan閱讀 9,876評論 10 75