深入理解RunLoop
什么是 RunLoop,RunLoop的应用范畴,RunLoop内部实现逻辑,RunLoop与线程的关系,timer与RunLoop的关系,RunLoop是怎么响应用户操作的,RunLoop的几种状态,RunLoop的mode作用是什么?
什么是RunLoop
顾名思义,RunLoop是运行循环的意思,就是在程序运行中循环做一些事情。
1 | int main(int argc, char * argv[]) { |
RunLoop的基本作用:保持程序的持续运行、处理App中的各种事件(eg:触摸事件、定时器事件)、节省CPU资源,提高程序性能:有事做做事,没事做休息
RunLoop的应用范畴
RunLoop主要应用场景有以下几个:
1> 定时器(Timer)、performSelector
2> GCD Asyc Main Queue
3> 事件响应、手势识别、界面刷新
4> 网络请求
5> AutoreleasePool
RunLoop 对象
iOS中有2套API来访问和使用RunLoop:
1> Foundation : NSRunLoop
2> Core Foundation : CFRunLoopRef1
2
3
4
5
6// 两种获取 RunLoop 方式
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
NSRunLoop *mainRunloop = [NSRunLoop mainRunLoop];
CFRunLoopRef runloop2 = CFRunLoopGetCurrent();
CFRunLoopRef mainRunloop2 = CFRunLoopGetMain();
NSRunLoop和CFRunLoopRef都代表着RunLoop对象,NSRunLoop是基于CFRunLoop的一层OC封装。
RunLoop与线程
1> 每条线程都有位移的一个与之对应的RunLoop对象
2> RunLoop保存在一个全局的Dictionary,线程作为key,RunLoop作为对象
3> 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建,主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
4> RunLoop会在线程结束时销毁
源码分析
打开刚才下载的源码,我们发现CF的库是没有xcode项目的。新建一个项目,讲所有文件拖进来。
搜索RunLoop.c文件,CFRunLoopGetMain(void)函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
...
// 如果不存在,create
if (NULL != rlm && NULL == rlm->_sources0) {
...
rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
}
// 方便理解,省略一些代码,感兴趣的同学可以深入研究
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
...
return loop;
}
里面调的是_CFRunLoopGet0(pthread_t t)这个函数,内部调用的(CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));,这个函数,根据方法名,我们可以猜测,应该是个字典,传入的参数是一个pthreadPointer线程指针,返回一个runloop对象,也证实了刚才所述的字典管理猜想。
RunLoop相关的类
Core Foundation中关于RunLoop的5个类
1> CFRunLoopRef
2> CFRunLoopModeRef
3> CFRunLoopSourceRef
4> CFRunLoopTimerRef
5> CFRunLoopObserverRef
CFRunLoopRef底层结构:1
2// CFRunLoopRef 底层是这种类型结构体
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
继续搜索 struct __CFRunLoop,会发现搜索出来很多,一个小技巧快速定位,在后面加一个空格和大括号:struct __CFRunLoop {1
2
3
4
5
6
7
8
9
10struct __CFRunLoop {
// 实际结构体成员比这个多,将几个重点的选出:
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
// 当前模式
CFRunLoopModeRef _currentMode;
// _models 存储了多个 CFRunLoopModeRef 对象,只有一个为 CurrentMode
CFMutableSetRef _modes;
};
我们发现一个RunLoop对象里面有一个pthread_t线程,所以每个线程都有一个与之对应的RunLoop;CFMutableSetRef上层与之对应NSMutableSet,与NSMutableArray用法类似,区别是数组有序,可以使用index进行取值;集合无序,取值只能通过[set anyObject]方式。
CFRunLoopModeRef底层结构是什么样子
跟刚才小技巧一样,我们直接搜索struct __CFRunLoopMode {1
2
3
4
5
6
7
8
9
10
11
12
13struct __CFRunLoopMode {
...
// 同样将本文关心之外的省略
// mode 名称
CFStringRef _name;
// _sources0/_sources0 集合存储CFRunLoopSourceRef对象
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
// _observers 集合存储 CFRunLoopObserverRef 对象
CFMutableArrayRef _observers;
// _timers 存储 CFRunLoopTimerRef 对象
CFMutableArrayRef _timers;
};
总结:
1> CFRunLoopModeRef代表着RunLoop的运行模式
2> 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
3> RunLoop启动时只能选择其中一个Mode,作为currentMode
4> 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入。这样做的好处是不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响,系统一个时刻只处理一种mode下面的事件
5> 如果一个Mode里面没有任何的Source0/Source1/Timer/Observer,RunLoop会立马退出
如下图:
常用的Mode
1> kCFRunLoopDefaultMode (NSDefaultRunLoopMode):App默认Mode,通常主线程是在这个Mode下运行
2> UITrackingRunLoopMode:界面跟踪Mode,用于Scrollow追踪触摸滑动,保证界面在滑动时不受其他Mode影响
3> kCFRunLoopCommonModes 通用模式,默认包含上面两种模式
Source0/Source1/Timers/Obvers处理什么事件
断点touchesBegan,打印堆栈,我们发现,时间调用从Source0开始,到最后的touchesBegan
Source0:
1> 触摸事件处理
2> performSelector:onThread:
Source1:
1> 基于Port的线程间通信
2> 系统时间捕捉(eg.上面的点击事件虽然最后是Source0处理的,但是最开始其实是Source1来进行捕捉的,然后分发给Source0来进行处理)
Timers:
1> NSTimer
2> performSelector:withObject:afterDelay:
Observers:
1> 用于监听RunLoop的状态
2> UI刷新(BeforeWaiting,在休眠之前刷新UI)
3> Autorelease pool(BeforeWaiting,休眠之前将池内需要操作的对象进行一次release操作)
RunLoop几种状态
1 | /* Run Loop Observer Activities */ |
监听 RunLoop 状态:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 创建 Observer
// C 函数方式
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observerRunLoopActivities, NULL);
// block 方式
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
});
// 添加 Observer
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
}
void observerRunLoopActivities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
case kCFRunLoopAllActivities:
NSLog(@"kCFRunLoopAllActivities");
break;
default:
break;
}
}