NSRunloop简单细说(六)—— 几种循环模式详细解析
版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.08.23 |
前言
NSRunloop
是OC Foundation
框架中非常重要的一个类,很多时候我们会使用它,但是未必对其有深入的了解,接下来几篇我就会带着大家重新学习一下NSRunloop
这个类,从简单到复杂,从基本到深化,我会一步步的走完。希望对大家有所帮助。感兴趣的可以看我上一篇。
1. NSRunloop简单细说(一)—— 整体了解
2. NSRunloop简单细说(二)—— 获取运行循环及其模式
3. NSRunloop简单细说(三)—— 定时器和端口
4. NSRunloop简单细说(四)—— 开启Runloop
5. NSRunloop简单细说(五)—— 调度和取消消息
几种循环模式简介
关于循环模式可以参考文档
运行循环模式是要监视的输入源和定时源的集合,以及要通知的运行循环观察器的集合。 每次运行运行循环时,都可以指定(显式地或隐式地)运行一个特定的“模式”。 在运行循环的通过期间,仅监视与该模式相关联的源,并允许其传递其事件。 (同样,只有与该模式关联的观察者才会被通知运行循环的进度。 与其他模式相关联的源将持续到任何新事件,直到以适当模式通过循环。
在您的代码中,您可以按名称识别模式。Cocoa
和Core Foundation
都定义了默认模式和几种常用模式,以及用于在代码中指定这些模式的字符串。 您可以通过简单地为模式名称指定自定义字符串来定义自定义模式。 虽然您分配给自定义模式的名称是任意的,但这些模式的内容不是。 您必须确保将一个或多个输入源,计时器或运行循环观察器添加到您创建的任何模式,使其有用。
您可以使用模式在特定通过运行循环的过程中过滤来自不需要的源的事件。 大多数情况下,您将要在系统定义的“默认”模式下运行运行循环。 但是,模态面板可能会以“模态”模式运行。 在此模式下,只有与模式面板相关的来源才会将事件传递给线程。 对于辅助线程,可以使用自定义模式来防止低优先级源在时间紧迫的操作期间传递事件。
这里还要注意:模式根据事件的来源进行区分,而不是事件的类型。 例如,您不会使用模式仅匹配鼠标向下事件或仅匹配键盘事件。 您可以使用模式来监听不同的端口集,临时暂停定时器,或以其他方式更改源和运行当前正在监视的循环观察器。
其实前面我们都已经简要的说了一下NSRunloop的几种循环模式了,这一篇主要就是扩展,详细的说一下这几种循环模式。
FOUNDATION_EXPORT NSRunLoopMode const NSDefaultRunLoopMode;
FOUNDATION_EXPORT NSRunLoopMode const NSRunLoopCommonModes NS_AVAILABLE(10_5, 2_0);
typedef NSString * NSRunLoopMode NS_EXTENSIBLE_STRING_ENUM;
系统为我们提供了多种模式,如下所示:
-
Default
模式- 定义:NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)描述:默认模式中几乎包含了所有输入源(NSConnection除外),一般情况下应使用此模式。
-
Connection
模式- 定义:
NSConnectionReplyMode(Cocoa)
描述:处理NSConnection对象相关事件,系统内部使用,用户基本不会使用。
- 定义:
-
Modal
模式- 定义:
NSModalPanelRunLoopMode(Cocoa)
描述:处理modal panels事件。
- 定义:
-
Event tracking
模式- 定义:UITrackingRunLoopMode(iOS)
NSEventTrackingRunLoopMode(cocoa)
描述:在拖动loop或其他user interface tracking loops时处于此种模式下,在此模式下会限制输入事件的处理。例如,当手指按住UITableView拖动时就会处于此模式。
- 定义:UITrackingRunLoopMode(iOS)
-
Common
模式- 定义:NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)描述:这是一个伪模式,其为一组run loop mode的集合,将输入源加入此模式意味着在Common Modes中包含的所有模式下都可以处理。在Cocoa应用程序中,默认情况下Common Modes包含
default modes
,modal modes
,event Tracking modes
。可使用CFRunLoopAddCommonMode方法想Common Modes中添加自定义modes。
- 定义:NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)描述:这是一个伪模式,其为一组run loop mode的集合,将输入源加入此模式意味着在Common Modes中包含的所有模式下都可以处理。在Cocoa应用程序中,默认情况下Common Modes包含
下面我们就详细的研究一下这几个运行模式。
一、NSDefaultRunLoopMode
这个是默认的运行模式,也是最常用的运行模式,NSTimer
与NSURLConnection
默认运行该模式下,所以如果我们不改变定时器的运行模式,让他在这个模式下运行,那么当我们拖动控件的时候,变为UITrackingRunLoopMode
模式时,NSTimer
就不能响应了,解决方法就是将定时器加入到UITrackingRunLoopMode
和NSRunLoopCommonModes
。另外,还有一种方法就是:将 Timer 加入到顶层的 RunLoop
的 commonModeItems
中。commonModeItems
被 RunLoop 自动更新到所有具有Common
属性的 Mode 里去。
默认模式是用于大多数操作的模式。 大多数情况下,您应该使用此模式启动运行循环并配置输入源。一般用于处理NSConnection
对象以外的输入源的模式
二、NSConnectionReplyMode
Cocoa
将此模式与NSConnection
对象结合使用来监视回复。 你很少需要自己使用这种模式。
三、NSModalPanelRunLoopMode
Cocoa使用此模式来识别用于模式面板的事件。
四、UITrackingRunLoopMode
Cocoa使用此模式在鼠标拖动循环和其他类型的用户界面跟踪循环期间限制传入事件。
五、NSRunLoopCommonModes
这是一组可配置的常用模式。 将输入源与此模式相关联还将其与组中的每种模式相关联。 对于Cocoa应用程序,默认情况下,此设置包括default
, modal
, 和event tracking
。 Core Foundation
最初只包含默认模式。 您可以使用CFRunLoopAddCommonMode
函数向该集合添加自定义模式。
主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
。这两个 Mode 都已经被标记为”Common”属性。
这里还要说一下底层的东西,CFRunLoopMode
,这个是NSRunLoopMode在C语言的底层,是一个结构体,如下所示。
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
下面也顺便看一下NSRunLoop的C语言底层。
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里。
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);
你只能通过 modeName
来操作内部的 mode,当你传入一个新的modeName
但 RunLoop 内部没有对应 mode 时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef
。对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除。
参考文章
1. iOS中NSRunLoop的模式
2. 深入理解RunLoop
后记
未完,待续~~~