RunLoop 是 iOS 開發中一個非常基礎而又重要的一個概念
- 為什么說它非常重要呢?
- 它不僅是檢驗一個程序員水平的知識點
- 它也是和
事件的傳遞和響應
、Runtime
一樣被作為面試的常見問題 - 而且,程序的入口
main()
函數本身其實內部就開啟了一個主運行循環,你可以理解為是一個死循環,它保證了程序不退出一直運行,可以說沒有runloop,那么程序一啟動就會閃退了 - 它可以節省CPU資源,提高程序的性能(原則是有事就做處理,沒事就休息)
- 由此可見其重要性
- 那為什么又說它非常基礎呢?
其實我們在開發中無時無刻不在接觸這個東西 - 我們在使用NSTimer的時候用到了這個
- 各種觸摸事件
- selector事件,比如performSelector
- 等等等等這些很基礎的東西都用到了它
- RunLoop--簡單認識
- 從字面意思上說,它就是運行循環,不斷的循環,不斷的跑圈處理事件
- 蘋果提供了2套API來訪問RunLoop
core foundation框架[CFRunloopRef]和foundation框架[NSRunloop]
- NSRunLoop是基于CFRunLoopRef的一層OC包裝,提供了面向對象的 API,但是這些 API 不是線程安全的。如果想要更深的了解RunLoop內部結構,建議多研究CFRunLoopRef層面的API(Core Foundation層面)
- RunLoop參考資料-官方文檔、 CFRunLoopRef開源代碼下載地址
- RunLoop--線程相關
- 一個Runloop對應著一條唯一的線程
- 線程本身是執行完自己的任務就死了,但是可以通過給線程開啟一個RunLoop保證其即使執行完自己任務也不死
- 程序啟動默認是已經開啟了一個Runloop,它是和主線程有關聯的
- 除了主線程的runloop是系統默認開啟的,其他的子線程的runloop需要自己手動創建,不然是不會開啟的
- runloop會在第一次獲取時被創建,在線程結束時被銷毀
- RunLoop--創建初識
- 獲取當前應用程序的主線程對應的Runloop
//NSRunloop類型
NSRunLoop * runloop = [NSRunLoop mainRunLoop];
//CFRunLoopRef類型
CFRunLoopRef runloop = CFRunLoopGetMain(); - 獲得當前線程的Runloop
//NSRunloop類型
NSRunLoop * runloop = [NSRunLoop currentRunLoop];
//CFRunLoopRef類型
CFRunLoopRef runloop = CFRunLoopGetCurrent(); - 上述方法里面需要注意的是,runloop的創建是直接使用currentRunLoop方法創建的,并不是普通的對象那種alloc init方法創建,而且其本身是一個懶加載
- 線程的runloop對象是通過字典來進行存儲的,字典的value自然 不必多說,就是對應的runloop對象,它的key就是對應的線程啦~
- RunLoop--相關類
- CFRunloopRef (對象)
- CFRunloopModeRef(Runloop的運行模式)
- CFRunloopSourceRef(Runloop要處理的事件源)
- CFRunloopTimerRef(Timer事件)
- CFRunloopObserverRef(Runloop的觀察者(監聽者))
RunLoop相關類關系圖.png
`
- CFRunLoopSourceRef 是事件產生的地方
Source有兩個版本:Source0(非基于端口) 和 Source1(基于端口) ;
Source0 只包含了一個回調(函數指針),它并不能自己主動觸發事件,一般用于需要用戶去主動觸發的事件。使用時,你需要先調用 CFRunLoopSourceSignal(source),將這個 Source 標記為待處理,然后手動調用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件;
Source1 包含了一個 mach_port 和一個回調(函數指針),被用于通過內核和其他線程相互發送消息。這種 Source 能主動喚醒 RunLoop 的線程 - CFRunLoopTimerRef 是基于時間的觸發器
它和 NSTimer 是差不多的。其包含一個時間長度和一個回調(函數指針)。當其加入到 RunLoop 時,RunLoop會注冊對應的時間點,當時間點到時,RunLoop會被喚醒以執行那個回調。
3)CFRunLoopObserverRef 是觀察者,每個 Observer 都包含了一個回調(函數指針),當 RunLoop 的狀態發生變化時,觀察者就能通過回調接受到這個變化。
可觀察狀態發生變化的幾個時間節點
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
};
`
- 對上述關系圖的一些解析(對于理解runloop個人感覺還是非常重要的)
- runloop內部有很多的模式,它可以任意切換不同的模式,每個模式里面又有很多的Timer、Source、Observer等對象,這些對象用以告訴runloop需要去處理什么事情,這樣runloop就可以監聽到不同的操作,如果它有被喚醒,那么它就一直循環的處理事件,否則就去睡覺
- 每次runloop啟動后只可以同時指定一個mode,并且Mode里面至少要有一個source或者一個timer,僅僅只有一個observer是不行的,不然程序一啟動還是會退出的,并且當前這個runloop被稱為currentRunloop
- 如果需要切換mode,那么只可以先退出當前的loop,再重新指定一個mode進入,這樣做的目的也很簡單,主要是為了區分不同組的timer、source、observer讓其互不影響
- RunLoop -- 邏輯處理
先上一幅比較能說明問題的圖片,這個是萬能的網友整理的,讓我想起了,當年大學英語四六級,每次考完試就有源源不斷的各種網友整理版答案
Runloop邏輯處理.png
對應邏輯.png
- RunLoop和NSTimer使用相關
- 先看一個系統默認注冊的五個模式
a.kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
b.UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
c.UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
d.GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到
e.kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode - 之所以先拿這個說明一些問題是因為定時器是我們平時使用比較多也比較基礎的東西
- NSTimer如果調用了scheduledTimer方法,那么會自動添加到當前的runloop里面去,而且runloop的運行模式為kCFRunLoopDefaultMode ,如需更改的話
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- NSTimer如果調用了timerWithTimeInterval方法,需要自己手動將其添加到對應的runloop運行模式里面
- 正常的規則是將定時器添加到什么模式下,它就在什么模式下有效,一旦切換模式,那么它就不工作
- 在主線程中操作定時器,因為主線程的操作默認都是在主運行循環中執行,主運行循環默認的模式是默認模式,這個時候如果處于拖拽模式就不會工作了
- 定時器設置為通用模式后,里面相當于包括了2個模式,普通和拖拽
- 但是這個并不違背每一個runloop只可以同時指定一個Mode,因為它默認是相當于把定時器分別添加到了普通和拖拽模式下,這樣無論是切換到普通還是拖拽,定時器都可以運作
- 常用的那種定時器創建方式(調度那種),系統默認是已經將其添加到默認模式了,如果我們想讓他無論是拖拽還是默認都起作用還需要將定時器添加到拖拽模式
結束語
OK,繁雜的文字說明暫時告一段落,實在沒辦法,runloop本身就是這樣的,需要用文字去說明其本身的含義,寫的不足之處歡迎指正,一起學習,這些是目的我對于runloop的基本理解,接下來會繼續寫關于runloop在iOS開發中的一些實際應用,比如自動釋放池,手勢識別,常駐線程、PerformSelecter等