runloop
- 一個運行循環,保證程序不退出
- 負責處理各種事件(source、timer、observer)
- 沒有事件處理則進入休眠,節省資源,有事件則喚醒處理
Core Foundation中關于RunLoop的5個類:
1、CFRunLoopRef - 獲得當前RunLoop和主RunLoop
2、CFRunLoopModeRef RunLoop - 運行模式,只能選擇一種,在不同模式中做不同的操作
3、CFRunLoopSourceRef - 事件源,輸入源
4、CFRunLoopTimerRef - 定時器時間
5、CFRunLoopObserverRef - 觀察者
runloop 源
1、輸入源:
- NSPort 基于端口的源
- 自定義源
- performSelector:OnThread
2、時間源
runloop mode
- 一個runloop可以有多個mode,每個mode又包含若干個source、timer、observer。
- runloop有5個mode:
- kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
- UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
- UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到
- kCFRunLoopCommonModes: 這是一個占位用的Mode,作為標記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一種真正的Mode
對外暴露的Mode為:NSDefaultRunLoopMode
和NSRunLoopCommonModes
Q:由于 UITabelView 在滑動的時候,會從當前的 RunLoop 默認的模式 kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 自動切換到 UITrackingRunLoopMode界面追蹤模式。這個時候,處于 NSDefaultRunLoopMode 里面的 NSTimer 由于切換了模式造成計時器無法繼續運行。
A:
1、更改RunLoop運行Mode(NSRunLoopCommonModes)
2、將NSTimer放到新的線程中
runloop source
- Port-Based Sources (端口)
- Custom Input (自定義事件)
- Cocoa Perform Selector Sources
按照函數的調用棧:
Source0:非基于Port的 用于用戶主動觸發的事件(點擊button 或點擊屏幕)
Source1:基于Port的 通過內核和其他線程相互發送消息(與內核相關)
注意:Source1在處理的時候會分發一些操作給Source0去處理
runloop observer
CFRunLoopObserverRef觀察者,監聽runloop的狀態。通過回調接收狀態變化。它不屬于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 應用
- 常駐線程
- AsyncDisplayKit, RunLoop 中添加一個 Observer,監聽了runloop狀態進行處理渲染
- 卡頓監控(同上)
- 子線程定時器
- performSelector:
當調用 NSObject 的performSelecter:afterDelay:
后,實際上其內部會創建一個 Timer 并添加到當前線程的 RunLoop 中。所以如果當前線程沒有 RunLoop,則這個方法會失效。
當調用performSelector:onThread:
時,實際上其會創建一個 Timer 加到對應的線程去,同樣的,如果對應線程沒有 RunLoop 該方法也會失效。
AutoreleasePool
App啟動后,蘋果在主線程 RunLoop 里注冊了兩個 Observer,其回調都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一個Observer監視Entry(即將進入runloop),其回調會調用_objc_autoreleasePoolPush() 創建自動釋放池。其 order 是-2147483647,優先級最高,保證創建釋放池發生在其他所有回調之前。
第二個Observer監視BeforeWaiting和Exit。
BeforeWaiting(準備進入休眠)時調用 _objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創建新池;
Exit(即將退出runloop)時調用 _objc_autoreleasePoolPop() 來釋放自動釋放池。這個 Observer 的 order 是 2147483647,優先級最低,保證其釋放池子發生在其他所有回調之后。
Q:自動釋放池什么時候釋放?
A:在runloop休眠前釋放(kCFRunLoopBeforeWaiting)