目录
- RunLoop基础知识
---- 什么是RunLoop
---- RunLoop的作用
---- 什么时候使用Runloop
---- iOS中的RunLoop的参与
---- 获取RunLoop对象
---- RunLoop和线程的关系- RunLoop的底层结构
---- CFRunLoopRef
---- CFRunLoopModeRef
---- CFRunLoopSourceRef
------------ source0
------------ source1
---- CFRunLoopObserverRef
------------ observer
---- CFRunLoopTimerRef
------------ timer- 补充
---- currentMode
---- commonModes
---- RunLoop的五种运行模式
---- 各个Model模式间的监听和切换
RunLoop基础知识
-
什么是RunLoop
- 顾名思义,就是运行循环,它会在程序运行过程中循环做一些事情。
- 通常情况下,单条线程一次只能执行一个任务,执行完成线程就会退出。如果我们希望有一个机制,让线程可以随时随地处理事件且不退出,这种模型通常称作
Event Loop
。Event Loop
在很多系统框架里面都有实现,比如Node.js的事件处理,Windows程序的消息循环,再比如iOS/OSX的RunLoop
。- 一个
RunLoop
就是一个事件处理的循环,用来不停的调度工作及处理输入事件。- 让线程在有工作的时候保持忙碌,在没有工作的时候进入休眠,这样做的好处就是让线程进入休眠之后避免资源占用。
iOS系统中有2套API来访问和使用RunLoop:
CFRunLoopRef
:是 CoreFoundation 框架内的,是一套纯 C 的 API。开源的【 Runloop源码】 。NSRunLoop
:基于 CFRunloopRef 封装而成。提供了面向对象的 API。NSRunLoop
和CFRunLoopRef
都代表着RunLoop
对象。
-
RunLoop的作用
- 保持程序的持续运行。
- 处理App中的各种事件(比如触摸事件、定时器事件等)。
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息。
- ......
-
什么时候使用Runloop
- 当需要和该线程进行交互的时候才会使用Runloop
-
iOS中的RunLoop的参与
新建一个iOS项目,我们打开main
函数,我们注释掉其它代码,改成如下:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}
结果打印:
Hello, World!
Program ended with exit code: 0
- 开始运行程序,我们APP启动之后便会闪屏退出。
- 控制台打印结果可以看出,执行完输出,
Program ended with exit code: 0
,应用会自动闪屏退出。
如果打开将代码恢复到原样:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
程序会一直处于运行中,随时接收用户的点击和交互事件,除非我们主动关闭该
App
应用。
总结:
- 没有
RunLoop
的参与,执行完NSLog(@"Hello, World!");
代码后,会即将退出程序。- 在OC代码中,Runloop是由系统默认开启的。
- 在main函数中的
UIApplicationMain
方法中自动创建一个RunLoop并开启,
我们模拟一下Runloop
的【伪代码】如下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
int retVal = 0;
do {
// 睡眠中等待
int message = sleep_and_wait();
// 处理消息
retVal = process_message(message);
}while (0 == retVal);
return 0;
}
}
-
获取RunLoop对象
获取当前runLoop
NSRunLoop * runLoop1 = [NSRunLoop currentRunLoop];
获取主线程runLoop
NSRunLoop * runLoop2 = [NSRunLoop mainRunLoop];
获取当前runLoop
CFRunLoopRef runLoop3 = CFRunLoopGetCurrent();
获取主线程runLoop
CFRunLoopRef runLoop4 = CFRunLoopGetMain();
分别打印NSRunLoop
和CFRunLoopRef
。
发现地址并不相同,证明了NSRunLoop
并不是等价于CFRunLoopRef
,而是对CFRunLoopRef
进行了封装。
NSLog(@"%p %p",[NSRunLoop currentRunLoop], CFRunLoopGetCurrent());
查看【 Runloop源码】,我下载的是CF-1153.18.tar.gz
。在CFRunLoop.c
中,
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
......
// 读取
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {// 没有的话就创建一个
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
}
......
return loop;
}
- 通过查看源码可知当,通过CFRunLoopGetCurrent读取当前RunLoop时,如果没有就创建一个新的RunLoop,并保存在
__CFRunLoops全局字典
中。__CFRunLoops字典
是以线程
作为key
,RunLoop
作为value
。
-
RunLoop和线程的关系
- 每条线程都有唯一的一个与之对应的RunLoop对象。
- RunLoop保存在一个全局的
Dictionary
里,线程作为key
,RunLoop作为value
。- 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它的时候创建。
- 主线程的RunLoop在
main
函数的UIApplicationMain
方法中已经自动获取(创建)。- 子线程默认没有开启RunLoop,除非在子线程主动调用
[NSRunLoop currentRunLoop]
函数。- RunLoop会在线程结束时销毁。
RunLoop的底层结构
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
这5个类之间的关系可以用下图来表示:
查看【 Runloop源码】,我下载的是CF-1153.18.tar.gz
。在CFRunLoop.c
中,RunLoop底层结构如下:
-
CFRunLoopRef
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
// CoreFoundation 中的 runtime 基础信息
CFRuntimeBase _base;
// 针对获取 mode 列表操作的锁
pthread_mutex_t _lock; /* locked for accessing mode list */
// 唤醒端口
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
// 是否使用过
Boolean _unused;
// runloop 运行会重置的一个数据结构
volatile _per_run_data *_perRunData; // reset for runs of the run loop
// runloop 所对应线程
pthread_t _pthread;
uint32_t _winthread;
// 存放 common mode 的集合
CFMutableSetRef _commonModes;
// 存放 common mode item 的集合
CFMutableSetRef _commonModeItems;
// runloop 当前所在 mode
CFRunLoopModeRef _currentMode;
// 存放 mode 的集合
CFMutableSetRef _modes;
// runloop 内部 block 链表表头指针
struct _block_item *_blocks_head;
// runloop 内部 block 链表表尾指针
struct _block_item *_blocks_tail;
// 运行时间点
CFAbsoluteTime _runTime;
// 休眠时间点
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
通过上述,我们可以知道:
- RunLoop也是一个结构体对象
_modes
:RunLoop可以有多个mode对象。_currentMode
:Runloop在同一时间只能且必须在某一种特定的Mode下面Run。- 更换Mode时,必须要停止当前的Loop,然后重启新的Loop。
- 重启的意思是退出当前的while循环,然后重新设置一个新的while,两者互不影响。
- 切换
Mode
不会导致程序退出。
-
CFRunLoopModeRef
Runloop 中可以包含若干个 Mode,每个 Mode 又包含若干的 Source/Timer/Observer
。
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
// CoreFoundation 中的 runtime 基础信息
CFRuntimeBase _base;
// 互斥锁,加锁前需要 runloop 先加锁
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
// mode 的名称
CFStringRef _name;
// mode 是否停止
Boolean _stopped;
char _padding[3];
// source0
CFMutableSetRef _sources0;
// source1
CFMutableSetRef _sources1;
// observers
CFMutableArrayRef _observers;
// timers
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
// port 的集合
__CFPortSet _portSet;
// observer 的 mask
CFIndex _observerMask;
// 如果定义了 GCD 定时器
#if USE_DISPATCH_SOURCE_FOR_TIMERS
// GCD 定时器
dispatch_source_t _timerSource;
// 队列
dispatch_queue_t _queue;
// 当 GCD 定时器触发时设置为 true
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
// 如果使用 MK_TIMER
#if USE_MK_TIMER_TOO
// MK_TIMER 的 port
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
// 定时器软临界点
uint64_t _timerSoftDeadline; /* TSR */
// 定时器硬临界点
uint64_t _timerHardDeadline; /* TSR */
};
总结:
CFRunLoopModeRef
代表RunLoop
的运行模式。CFRunLoopModeRef
可以视为事件的管家,一个Mode管理着各种事件。CFRunLoopModeRef
核心内容是4个数组容器,分别用来装Source0、Source1、Observer、Timer
。- 一个
RunLoop
包含若干个Mode
,每个Mode
又包含了若干个Source0、Source1、Observer、Timer
。_sources0
和_sources1
里保存着CFRunLoopSourceRef
对象。_observers
保存着CFRunLoopObserverRef
对象。_timers
保存着CFRunLoopTimerRef
对象。RunLoop
启动时只能选择一个Mode
,作为currentMode
。- 如果一个Mode中一个
Source0、Source1、Observer、Timer
都没有,那么 Runloop会直接退出,不进入事件循环。- 如果切换
Mode
,只能退出当前Loop,再重新选择一个Mode进入。这样可以使不同组的Source0、Source1、Timer、Observer
能分隔开来,互不影响。- 切换
Mode
不会导致程序退出。
-
CFRunLoopSourceRef
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
struct __CFRunLoopSource {
// CoreFoundation 中的 runtime 基础信息
CFRuntimeBase _base;
uint32_t _bits;
// 互斥锁
pthread_mutex_t _lock;
// source 的优先级,值为小,优先级越高
CFIndex _order; /* immutable */
// runloop 集合
CFMutableBagRef _runLoops;
// 一个联合体,说明 source 要么为 source0,要么为 source1
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
CFRunLoopSourceRef
是事件源(输入源)。它是事件产生的地方,输入源是将事件异步传递到线程中。事件的源则取决于输入源的类型。
输入源一般分为两类:
source0
:自定义输入源,负责触摸事件处理。source1
:基于Port(端口)的输入源。,负责系统事件捕捉和Port(端口)的线程间通信。
-
Source1
Source1
包含了一个mach_port
和一个回调指针,可以监听系统端口和通过内核和其他线程通信,接收、分发系统事件。- 它能够主动唤醒RunLoop(由操作系统内核进行管理,例如CFMessagePort消息)
- 简单来说,Source1其实就是用来接收系统发出的事件(例如手机的触摸、摇晃或者锁屏等等)。
当App在前台静止时,如果我们点击App的页面,此时我们首先接触的是手机屏幕,点击屏幕会产生一个系统事件,通过source1
捕捉后由Springboard
程序包装成source0
分发给应用处理,因此我们在App
内部接收到的触摸事件都是source0
。
-
Source0
在iOS项目的ViewController
中,增加touchesBegan
事件。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
}
我们在NSLog
出增加断点,来查看函数调用栈:
我们发现函数从1直接到16
,我们在控制台使用【 LLDB指令】进行查看。
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000102a35f28 iOSStudy`-[ViewController touchesBegan:withEvent:](self=0x0000000144909b30, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x000000028154c000) at ViewController.m:22:5
frame #1: 0x0000000192753924 UIKitCore`forwardTouchMethod + 344
frame #2: 0x00000001927537a8 UIKitCore`-[UIResponder touchesBegan:withEvent:] + 64
frame #3: 0x00000001927623f0 UIKitCore`-[UIWindow _sendTouchesForEvent:] + 496
frame #4: 0x0000000192763f44 UIKitCore`-[UIWindow sendEvent:] + 3976
frame #5: 0x000000019273d2cc UIKitCore`-[UIApplication sendEvent:] + 712
frame #6: 0x00000001927c71ec UIKitCore`__dispatchPreprocessedEventFromEventQueue + 7360
frame #7: 0x00000001927ca1a4 UIKitCore`__processEventQueue + 6460
frame #8: 0x00000001927c1650 UIKitCore`__eventFetcherSourceCallback + 160
frame #9: 0x000000018fce576c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
frame #10: 0x000000018fce5668 CoreFoundation`__CFRunLoopDoSource0 + 208
frame #11: 0x000000018fce4960 CoreFoundation`__CFRunLoopDoSources0 + 268
frame #12: 0x000000018fcdea8c CoreFoundation`__CFRunLoopRun + 824
frame #13: 0x000000018fcde21c CoreFoundation`CFRunLoopRunSpecific + 600
frame #14: 0x00000001a77e2784 GraphicsServices`GSEventRunModal + 164
frame #15: 0x000000019271cfe0 UIKitCore`-[UIApplication _run] + 1072
frame #16: 0x0000000192722854 UIKitCore`UIApplicationMain + 168
frame #17: 0x0000000102a361b0 iOSStudy`main(argc=1, argv=0x000000016d3cf8a0) at main.m:17:12
frame #18: 0x000000018f99e6b0 libdyld.dylib`start + 4
(lldb)
可以看到 Runloop 正在处理的触摸事件是一个source0
。
-
CFRunLoopObserverRef
FRunLoopObserverRef
是观察者/监听器,用于监听Runloop
的状态。如果Runloop 状态变更会通知监听者进行函数回调。每个Observer都包含了一个回调(函数指针)。比如:UI刷新就是通过监听器来实现的。
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver {
// CoreFoundation 中的 runtime 基础信息
CFRuntimeBase _base;
// 互斥锁
pthread_mutex_t _lock;
// observer 对应的 runloop
CFRunLoopRef _runLoop;
// observer 观察了多少个 runloop
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
// observer 优先级
CFIndex _order; /* immutable */
// observer 回调函数
CFRunLoopObserverCallBack _callout; /* immutable */
// observer 上下文
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
当RunLoop的状态发生变化时,观察者就能通过回调接受到这个变化。主要是用来向外界报告Runloop当前的状态的更改。
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop 1
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer 2
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source 4
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠 32
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒 64
kCFRunLoopExit = (1UL << 7), // 即将退出Loop 128
};
在代码中,我们可以使用以下方式来监听RunLoop的状态变化:
// 1.先创建一个 observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:{
NSLog(@"即将进入loop");
break;
}
case kCFRunLoopBeforeTimers:{
NSLog(@"即将处理Timer");
break;
}
case kCFRunLoopBeforeSources:{
NSLog(@"即将处理Source");
break;
}
case kCFRunLoopBeforeWaiting:{
NSLog(@"即将进入休眠");
break;
}
case kCFRunLoopAfterWaiting:{
NSLog(@"从休眠中唤醒");
break;
}
case kCFRunLoopExit:{
NSLog(@"即将退出loop");
break;
}
default:
break;
}
});
// 2 .给当前RunLoop添加观察者:监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
// 3.释放Observer
CFRelease(observer);
例如监听到
Runloop
状态为BeforeWaiting
就会刷新UI界面。
-
CFRunLoopTimerRef
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval;
CFTimeInterval _tolerance;
uint64_t _fireTSR;
CFIndex _order;
CFRunLoopTimerCallBack _callout;
CFRunLoopTimerContext _context;
};
CFRunLoopTimerRef
是基于时间的触发器,它和NSTimer
是toll-free bridged
的,可以混用。其包含一个时间长度和一个回调(函数指针)。- 当其加入到
RunLoop
时,RunLoop
会注册对应的时间点,当时间点到时,RunLoop
会被唤醒以执行那个回调。- NSTimer 的执行会受 Mode 影响。GCD 的定时器受 Runloop 的 Mode影响。
- 方法验证
performSelector:withObject:afterDelay:
Timer
里面的是 CFRunLoopTimerRef
,包括了定时器事件、[performSelector: withObject: afterDelay:]
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(run) withObject:nil afterDelay:1.0];
}
- (void)run{
NSLog(@"-----run-----");
}
增加断点和bt
命令,打印如下:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000102b45f5c iOSStudy`-[ViewController run](self=0x0000000103208dc0, _cmd="run") at ViewController.m:23:5
frame #1: 0x00000001910e4454 Foundation`__NSFireDelayedPerform + 416
frame #2: 0x000000018fce5fa0 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 32
frame #3: 0x000000018fce5ba0 CoreFoundation`__CFRunLoopDoTimer + 1064
frame #4: 0x000000018fce4ffc CoreFoundation`__CFRunLoopDoTimers + 328
frame #5: 0x000000018fcdeee4 CoreFoundation`__CFRunLoopRun + 1936
frame #6: 0x000000018fcde21c CoreFoundation`CFRunLoopRunSpecific + 600
frame #7: 0x00000001a77e2784 GraphicsServices`GSEventRunModal + 164
frame #8: 0x000000019271cfe0 UIKitCore`-[UIApplication _run] + 1072
frame #9: 0x0000000192722854 UIKitCore`UIApplicationMain + 168
frame #10: 0x0000000102b461bc iOSStudy`main(argc=1, argv=0x000000016d2bf8a0) at main.m:17:12
frame #11: 0x000000018f99e6b0 libdyld.dylib`start + 4
(lldb)
通过打印可以看出有CFRunLoopTimerRef
的参与。
-
补充
-
_currentMode
- RunLoop 对象内部的
_currentMode
指向了该RunLoop
的其中一个RunLoopMode
,就是当前运行中的 RunLoopMode。- RunLoop 当前只会执行
_currentMode
包含的事件(source0、source1、observer、timer)
。
-
_commonModes
- 一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。
- 每当
RunLoop
的内容发生变化时,RunLoop
都会自动将_commonModeItems
里的Source/Observer/Timer
同步到具有Common”
标记的所有Mode里。
-
RunLoop的五种运行模式
系统注册了5个Mode分别如下:
1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行。
2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode。
4. GSEventReceiveRunLoopMode: 接收系统事件的内部 Mode,通常用不到。
5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode。
在iOS中常见的2中Model:
kCFRunLoopDefaultMode
-
UITrackingRunLoopMode
。
这两个 Mode 都已经被标记为
Common
属性。
DefaultMode 是 App 平时所处的状态。
TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。
我们可以用这两个 Mode Name 来操作其对应的 Mode。
-
kCFRunLoopCommonModes
kCFRunLoopCommonModes
模式等效于kCFRunLoopDefaultMode
和UITrackingRunLoopMode
的结合。
它不是一个具体的模式,它可以理解成一个标签。
打上标签的RunLoopMode
会被放入到 RunLoop 内部的 _commonModes。
而放到_commonModes的作用可以参考上面。
-
各个Model模式间的监听和切换
问题思考,我们知道:
RunLoop
启动时只能选择一个Mode
,作为currentMode
。- 如果切换
Mode
只能退出当前Loop
,再重新选择一个Mode进入。这样可以使不同组的Source0、Source1、Timer、Observer
能分隔开来,互不影响。
下面我们来模拟一个实际案例:
操作一:启动一个定时器,每秒执行 `run`方法输出,这时系统会把该事件添加到` DefaultMode` 中,`Timer` 会得到重复调用。
操作二: 滑动一个`TableView`,`RunLoop `会将 `mode `切换为 `TrackingRunLoopMode`。
代码层面,我们XIB中拖入一个UITableVIew
,开启一个定时器,然后运行程序:
- (void)viewDidLoad {
[super viewDidLoad];
// 开启一个定时器,每隔一秒执行run方法
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit - %@", mode);
CFRelease(mode);
break;
}
default:
break;
}
});
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
}
- (void)run {
NSLog(@"-----run-----");
}
开始拖动时:
-----run-----
-----run-----
-----run-----
kCFRunLoopExit - kCFRunLoopDefaultMode
kCFRunLoopEntry - UITrackingRunLoopMode
结束拖动时:
kCFRunLoopExit - UITrackingRunLoopMode
kCFRunLoopEntry - kCFRunLoopDefaultMode
-----run-----
-----run-----
模拟结果:
视图不滑动时输出正常,但是当我们滑动视图时输出停止了。
如果需要滑动的过程中同时处理定时器事件,在两个 Mode 中都能得到回调:
方法一:将 NSTimer 也加入到 UITrackingRunLoopMode(但这样timer被添加了两次,不是同一个timer)。
方法二:将 Timer 加入到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到所有具有”Common”属性的 Mode 里去。即:把 timer
添加到 NSRunLoopCommonModes
。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
此时 timer 会被放入 RunLoop 的 _commonModeItems 里。只要运行到 _commonModes 所包含的某个 RunLoopMode ,就会去处理 _commonModeItems里面的事件,RunLoopMode 本身的事件也会处理。
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。
- 其内部的 mode 只能增加不能删除。
网友评论