深入理解RunLoop
Mac&iOS之多線程
CFRunLoop運用
RunLoop 的概念
- runloop是一個對象,管理需要處理的事件和消息
NSRunloop是CGRumLoopRef的封裝,提供面向對象API - 不可直接創建runloop,只能用CFRunLoopGetMain() 和 CFRunLoopGetCurrent()獲取
Runloop 與線程關系
- 線程和RunLoop一一對應,只能在一線程中獲取對應的runloop
RunLoop 對外的接口
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
一個Runloop包含若干個mode
mode里有若干個Source,Timer, Observer
CFRunLoopSourceRef 事件產生的地方
source0(不能主動觸發事件)
source1,包含一個match_port和一個回調(函數指針)能主動喚醒runloop
CFRunLoopTimerRef
基于時間的觸發器,包括時間長度和一個回調(函數指針)CFRunLoopObserverRef
每個Observe都包含一個回調(函數指針),當 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
};
上面的Source,Timer, Observer統稱為mode item,一個item可以同時加入多個mode,如果一個mode中一個item都沒有 將會退出runloop
有待考證
RunLoop 的 Mode
CFRunLoopMode 和 CFRunLoop的結構大致如下
struct __CFRunLoopMode {
CFStringRef _name;
// Mode Name, 例如 @“kCFRunLoopDefaultMode"和
UITrackingRunLoopMode
CFMutableSetRef _sources0;
// Set
CFMutableSetRef _sources1;
// Set
CFMutableArrayRef _observers;
// Array
CFMutableArrayRef _timers;
// Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes;
// Set 被標記為“common”屬性的mode講添加到這里
CFMutableSetRef _commonModeItems;
// Set 每次進入 runlOOP 將這些item里(source,timer,observer)的內容同步到標記為“common”標記的mode里
CFRunLoopModeRef _currentMode;
// Current Runloop Mode
CFMutableSetRef _modes;
// Set
...
};
_commonModes:一個mode可以被添加到這里,每次runloop發生變化時,都會講_commonModeItems(source,timer,observer)的內容同步到這些mode里
應用場景舉例:主線程的 RunLoop 里有兩個預置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。這兩個 Mode 都已經被標記為"Common"屬性。DefaultMode 是 App 平時所處的狀態,TrackingRunLoopMode 是追蹤 ScrollView 滑動時的狀態。當你創建一個 Timer 并加到 DefaultMode 時,Timer 會得到重復回調,但此時滑動一個TableView時,RunLoop 會將 mode 切換為 TrackingRunLoopMode,這時 Timer 就不會被回調,并且也不會影響到滑動操作。
有時你需要一個 Timer,在兩個 Mode 中都能得到回調,一種辦法就是將這個 Timer 分別加入這兩個 Mode。還有一種方式,就是將 Timer 加入到頂層的 RunLoop 的 "commonModeItems" 中。"commonModeItems" 被 RunLoop 自動更新到所有具有"Common"屬性的 Mode 里去。
RunLoop 的內部邏輯##
實際上 RunLoop 就是這樣一個函數,其內部是一個 do-while 循環。當你調用 CFRunLoopRun() 時,線程就會一直停留在這個循環里;直到超時或被手動停止,該函數才會返回。