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 的運行模式;
2.一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個
Source0/Source1/Timer/Observer;
3.RunLoop 啟動時只能選擇其中一個 Mode,作為 currentMode;
4.如果需要切換 Mode,只能退出當前 RunLoop,再重新選擇一個 Mode 進入;
5.不同組的
Source0/Source1/Timer/Observer 能分割開來,互不影響;
6.如果 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 知識點比較多,如果寫太多不利於大家的閱讀和消化,所以其他內容放在後面介紹!
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/233053.html