什么是 RunLoopMode
RunLoopMode 可以理解成為一個集合, 包括所有要監視的事件源和要通知的 RunLoop 中注冊的觀察者.
每次運行 RunLoop 時,都需要顯示或者隱式的指定其運行在哪一種 Mode(RunLoop 每次只能運行在一個 mode中).
在設置 RunLoopMode 以后,你的 RunLoop 就會自動過濾和其他 Mode 相關的事件源,而只監視和當前設置 Mode 相關的源(以及通知相關的觀察者).大多數時候 RunLoop 都運行在系統定義的默認的模式上.
在代碼中,你可以通過mode 名稱區分不同的 mode. Cocoa & CoreFoundation 框架通過不同名稱(NSString,CFString)定義了缺省 mode 和一系列其他的 mode.你也可以使用不同的名稱,定義自己的 mode,然后在這個 mode 中添加一些 source 以及 observer.使用這些 modes 可以從不想要的事件源中過濾事件.大多數情況下,我們都將 runloop 設置成default mode.
RunLoopMode 是基于事件的source源頭,而不是事件的type類型去區分的.比如你不能通過 RunLoopMode 去只選擇鼠標點擊事件或者鍵盤輸入事件.你可以使用 RunLoopMode 去監聽端口,暫停計時器或者或者改變添加或刪除一些 mode 中關注的 sources or observers.
Cocoa 和 CoreFoundation 為我們定義了默認和常用的 Mode.RunLoopMode 的名稱可以使用字符串來標識,我們也可以使用字符串指定一個 Mode 名稱來自定義 Model.
下面列出iOS 中已經定義的 RunLoopMode:
NSDefaultRunLoopMode,kCFRunLoopDefaultMode: 大多數工作中默認的運行方式。
NSConnectionReplyMode: 使用這個Mode去監聽NSConnection對象的狀態,我們很少需要自己使用這個Mode。
NSModalPanelRunLoopMode: 使用這個Mode在Model Panel情況下去區分事件(OS X開發中會遇到)。
UITrackingRunLoopMode: 使用這個Mode去跟蹤來自用戶交互的事件(比如UITableView上下滑動)。
GSEventReceiveRunLoopMode: 用來接受系統事件,內部的Run Loop Mode。
NSRunLoopCommonModes, kCFRunLoopCommomModes: 這是一個偽模式,其為一組run loop mode的集合。如果將Input source加入此模式,意味著關聯Input source到Common Modes中包含的所有模式下。在iOS系統中NSRunLoopCommonMode包含NSDefaultRunLoopMode、NSTaskDeathCheckMode、>UITrackingRunLoopMode.同時,我們可以使用 CoreFoundation 中的 CFRunLoopAddCommomMode()函數,將自定義的 mode 加入其中。
Cocoa 和 CoreFoundation 為我們定義了默認和常用的 Mode.RunLoopMode 的名稱可以使用字符串來標識,我們也可以使用字符串指定一個 Mode 名稱來自定義 Model.
注意 RunLoop 運行時只能以一種固定的 Mode運行,只會監控這個 Mode 下添加的 Timer source 和 Input >source.如果這個 Mode下沒有添加時間源, RunLoop 就會立即返回.
RunLoop 不能運行在 NSRunLoopCommonModes,因為 NSRunLoopModes 是個 Mode 的集合,而不是一個具>體的 Mode.我們可以在添加事件源的時候使用 NSRunLoopCommomModes,只要 RunLoop 運行在 >NSRunLoopModes 中任何一個 Mode,這個事件源就會被觸發.
RunLoopMode 的結構和分析
CFRunLoopMode 和 CFRunLoop 的結構大致如下:
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
這里有個概念叫 "CommonModes":一個 Mode 可以將自己標記為"Common"屬性(通過將其 ModeName 添加到 RunLoop 的 "commonModes" 中)。每當 RunLoop 的內容發生變化時,RunLoop 都會自動將 _commonModeItems 里的 Source/Observer/Timer 同步到具有 "Common" 標記的所有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 里去。
CFRunLoop對外暴露的管理 Mode 接口只有下面2個:
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);
Mode 暴露的管理 mode item 的接口有下面幾個:
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
你只能通過 mode name 來操作內部的 mode,當你傳入一個新的 mode name 但 RunLoop 內部沒有對應 mode 時,RunLoop會自動幫你創建對應的 CFRunLoopModeRef。對于一個 RunLoop 來說,其內部的 mode 只能增加不能刪除。
蘋果公開提供的 Mode 有兩個:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用這兩個 Mode Name 來操作其對應的 Mode。
同時蘋果還提供了一個操作 Common 標記的字符串:kCFRunLoopCommonModes (NSRunLoopCommonModes),你可以用這個字符串來操作 Common Items,或標記一個 Mode 為 "Common"。使用時注意區分這個字符串和其他 mode name。