什么是 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。