中高級 iOS 必備知識點之 RunLoop

RunLoop 學習起來是很抽象, 也不好理解, 所以一定多看幾次, 多學學才能學好! 這也是中高級 iOS 必須掌握的知識點, 面試中經常遇到.

什麼是 RunLoop?

Run 表示運行,Loop 表示循環。結合在一起就是運行循環的意思。RunLoop 就是在程序運行過程中循環做一些事情.

RunLoop 的應用範疇有哪些?

定時器 (Timer)、PerformSelector

GCD Async Main Queue

事件響應、手勢識別、界面刷新

網絡請求

AutoreieasePool

上面這些底層都是 RunLoop 在支撐, 說白了, 如果沒有 RunLoop 支撐, 上面的這些都無法實現.

如果沒有 RunLoop 會發生什麼呢? 像我們的命令行項目, 創建出來默認就是沒有 RunLoop, 請看下圖

因爲沒有 RunLoop, 程序執行到第 13 行的時候, 就會自動退出.

而我們 iOS 項目的 main 函數里面都有 UIApplicationMain(argc, argv,nil, appDelegateClassName); 這個代碼, 這裏就是創建了一個主線程的 RunLoop, 所以我們程序不會退出, 一直在運行中. 我們可以大致寫一下 main 函數里面的僞代碼如下:

retVal 這個等於 0, 當沒有事件處理的時候, RunLoop 就會 sleep 就是類似睡覺, 一旦有事件需要處理, 比如點擊、刷新事件等 process_message 就會去處理這個事件, 處理完了繼續休息, retVal=0, 程序就會一直執行, 不會退出, 這就是 RunLoop 作用.

RunLoop 的基本作用

  1. 保持程序的持續運行

  2. 處理 App 中的各種事件 (比如觸摸事件、定時器事件等)

  3. 節省了 CPU 資源, 提高程序性能: 該做事時做事, 該休息時休息

...

獲取 RunLoop 對象

iOS 中有 2 套 API 來訪問和使用 RunLoop :

Foundation : NSRunLoop (OC 語言裏面的)

Core Foundation : CFRunLoopRef (C 語言裏面的)

NSRunLoop 和 CFRunLoopRef 都代表着 RunLoop 對象

NSRunLoop 是基於 CFRunLoopRef 的一層 OC 包裝

CFRunLoopRef 是開源的.(CFRunLoopRef 參考鏈接)

其實我們很多都是由 OC 包裝出來的, 請看下面:

獲取當前的 RunLoop

獲取當前 RunLoop 和主線程 RunLoop

獲取 RunLoop

這裏注意, 地址不一樣, 因爲 NSRunLoop 是對 CFRunLoopDef 做了一層包裝, 你可以用 OC 的 NSLog("%@",[NSRunLoop MainRunLoop]) 獲取對比一下, 它的地址就是 C 語言獲取的地址. 主線程只有一個 RunLoop.

RunLoop 與線程

每條線程都有唯一的一個與之對應的 RunLoop 對象 (一一對應)

RunLoop 保存在一個全局的 Dictionary 裏, 線程作爲 key,RunLoop 作爲 value

線程剛創建的時候並沒有 RunLoop 對象, RunLoop 會在第一次獲取它時創建

RunLoop 會在線程結束時銷燬

主線程的 RunLoop 已經自動創建, 子線程默認沒有開啓 RunLoop.

源碼窺探看一下: CFRunLoopGetCurrent

由於源碼不能像 objc 直接打開, 我們把它拉到項目中查看.

從字典也能看出來是一對一的關係. 而且確實是第一次獲取的時候是空的, 然後再去創建這個 RunLoop.

那我們就繼續來了解 RunLoop 內部的數據結構, 到底是怎麼工作的.

RunLoop 相關的類

Core Foundation 中關於 RunLoop 的 5 個類

1.CFRunLoopRef

2.CFRunLoopModeRef

3.CFRunLoopSourceRef

4.CFRunLoopTimerRef

5.CFRunLoopObserverRef

再看下 CFRunLoopRef 的底層源碼:

就是上面這個結構體, 我們用到的可能就是紅色這些. pthread 是線程, 每個 runloop 都會保存這個東西. 最後面那個_modes, 這個是個集合來着, CFMutableSetRef 我們能想到我們自己用的 set 也是一個集合來着, 比如 NSMutableSet 也是一個集合, 所以這個_modes 裏面是存着一堆的 mode.

這個 mode 就是 CFRunLoopModeRef 類型, 所以裏面存儲一堆的 CFRunLoopModeRef 類型的 mode.

而_currentMode 也是 CFRunLoopModeRef 這個類型, 所以我們很容易得出一個結論:

一個 RunLoop 對象裏面有一堆的 mode, 也就是存在_modes 裏面, 裏面只有一個是_currentMode.

我們再窺探一下源碼, 看下 mode 裏面存儲的是什麼?

所以我們來個總結的圖:

RunLoop 有很多種模式, 對應的_currentMode 只有一種.

CFRunLoopModeRef

1.CFRunLoopModeRef 它是代表 RunLoop 的運行模式

  1. 一個 RunLoop 包含若干個 Mode, 每個 Mode 又包含若干個 Source0/Source1/Timer/Observer

3.RunLoop 啓動時只能選擇其中一個 Mode, 作爲 currentMode

  1. 如果需要切換 Mode, 只能退出當前 RunLoop, 再重新選擇一個 Mode 進入

  2. 不同組的 Source0/Source1/Timer/Observer 能分割開來, 互不影響

  3. 如果 Mode 裏面沒有任何 Source0/Source1/Timer/Observer,RunLoop 會立馬退出

如果只能在一種模式下運行, 對性能什麼的都有很大好處, 比如我在滑動模式下, 不考慮不滑動的模式, 所以就不會卡頓, 順暢很多. 還有注意的就是, 它切換 mode 是在循環裏面切換的, 所以不會導致程序退出.

常見的 mode 有 2 種, 其他情況很少見, 所以掌握這兩個一般都是沒問題了

1.KCFRunLoopDefaultMode (NSDefaultRunLoopMode):App 的默認 Mode, 通常是主線程是在這個 Mode 下運行

2.UITrackingRunLoopMode : 界面跟蹤 Mode, 用於 ScrollView 追蹤觸摸滑動, 保證界面滑動時不受其他 Mode 影響

RunLoop 到底做哪些事?

RunLoop 在不停執行的時候到底具體做了哪些事? 其實是 RunLoop 在不停循環的時候, 就是處理每個 mode 下的 Source0、Source1、Timer、Observer 這裏面的事件, 那我們就來看看這裏面具體對應的到底是什麼事件.

Source0

觸摸事件、performSelector:onThread:

比如我們的 touchbegin 這個我們看下下面的代碼:

Source1

基於 Port 的線程間的通信, 系統事件的捕捉.

(兩個線程之間相互傳遞消息的處理, 系統事件捕捉, 其實也包括觸摸事件, 只是把事件捕捉到以後傳遞給 Source0).

Timer

NSTimer 定時器, performSelector:withObject:afterDelay(這個方法的底層實現也就是 NSTimer 來實現的)

Observers

用於監聽 RunLoop 的狀態, UI 的刷新 (BeforeWaiting),Autorelease pool(BeforeWaiting)

(在 RunLoop 休眠之前都會去執行 UI 的刷新啊、Autorelease pool 的釋放等)

以上這些東西, 完全就是我們平時開發中經常寫的代碼, 比如設置背景色, 設置 frame 等等.

由於 RunLoop 知識點比較多, 如果寫太多不利於大家的閱讀和消化, 所以其他內容放在後面介紹!

轉自:掘金  GDCoder

https://juejin.cn/post/6948676084879589389

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/pTHB4C5EGK8sYPmRjkS-tg