RunLoop是iOS開發中的一個基礎概念,一個程序運行后,你動則它動,你不動它不動,這種時刻待命的效果,就是RunLoop的作用了。
首先什么是RunLoop,它的字面意思是一個運行循環,用來保持程序的運行,處理APP中的各種事件(如觸摸,定時器等),可以提高程序性能,達到勞逸結合。iOS中提供了NSRunLoop和CFRunLoopRef兩個對象來訪問和使用RunLoop。
每條線程都有唯一的一個與之對應的RunLoop對象。主線程的RunLoop已經自動創建好了,子線程的RunLoop需要主動創建。RunLoop在第一次獲取時創建,在線程結束時銷毀,因為該方法本身是懶加載,如果第一次調用該方法,那么會創建子線程對應的runloop并使用字典把線程對象和runloop保存起來,后面調用的時候就直接取值。
// 獲得當前線程的RunLoop對象
[NSRunLoop currentRunLoop];
// 獲得主線程的RunLoop對象
[NSRunLoop mainRunLoop];
// 獲得當前線程的RunLoop對象
CFRunLoop GetCurrent();
// 獲得主線程的RunLoop對象
CFRunLoop GetMain();
蘋果官方文檔
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
Core Foundation中關于RunLoop的共有5個類
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
下面我們來了解一下它們的特點。
其中CFRunLoopModeRef類并沒有對外暴露,只是通過 CFRunLoopRef 的接口進行了封裝,它代表RunLoop的運行模式。
1.CFRunLoopModeRef代表RunLoop的運行模式
2.每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode
3.如果需要切換Mode,只能退出Loop,再重新指定一個Mode進入
4.這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響
5.一個 RunLoop 包含若干個 Mode,每個Mode又包含若干個Source/Timer/Observer
6.RunLoop中如果沒有事件源,即沒有Source也沒有Timer,那么它一啟動就會退出。
系統為我們默認注冊了5個Mode:
1.kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
2.UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
3.UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
4.GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到
5.kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode
有一個比較常見的問題,實現輪播效果時,有時候會出現NSTimer不好用的問題。其內部原因是因為,拖拽scrollView時RunLoop會切換到UITrackingRunloopMode的界面追蹤模式,而我們的定時器是添加到kCFRunDefaultMode模式下的,所以才導致這個問題的出現。我們在這里只要使用NSRunLoopCommonModes模式,把定時器添加到這個模式下,就能避免這個問題。因為把timer添加到被標記為NSRunLoopCommonModes的運行模式下,這個模式默認包括tracking和defaul兩種。
**CFRunLoopSourceRef **是事件產生的地方,現在有兩種劃分方式:
Source0:event事件,只含有回調,需要標記待處理(signal),然后手動將runloop喚醒(wakeup)
Source1:包含一個 mach_port 和一個回調,被用于通過內核和其他線程發送的消息,能主動喚醒runloop
(以前的劃分方式為:port,custom,performSelector)
CFRunLoopTimerRef是基于時間的觸發器
NSTimer和performSEL方法實際上是對CFRunloopTimerRef的封裝
CFRunLoopObserverRef是用來監聽RunLoop的狀態改變的,一般是以下幾種:
// 進入runloop
kCFRunLoopEntry = (1UL << 0)
// 即將處理time事件
kCFRunLoopBeforeTimers = (1UL << 1)
// 即將處理source事件
kCFRunLoopBeforeSources = (1UL << 2)
// 即將休眠
kCFRunLoopBeforeWaiting = (1UL << 5)
// runloop被喚醒
kCFRunLoopAfterWaiting = (1UL << 6)
// runloop退出
kCFRunLoopExit = (1UL << 7)
每個 Observer 都包含了一個回調(函數指針),當 RunLoop 的狀態發生變化時,觀察者就能通過回調接受到這個變化。
如圖為RunLoop的邏輯處理過程: