1. RunLoop定义
imageRunLoop:运行循环,在程序运行过程中循环做一些事情。所涉及的范畴包括:
① 定时器(Timer)、PerformSelector;
② GCD Async Main Queue;
③ 事件响应、手势识别、界面刷新;
④ 网络请求;
⑤ AutoreleasePool。
通常情况下如果我们创建一个Command Line Tool项目,如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
程序在执行到Line13之后,马上会执行Line15,然后程序退出,因为此段代码中没有runloop.
但是在我们的iOS项目中并不是和Command Line Tool一样写main函数的.
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
我们可以通过runloop的源码得知runloop的原理,所以我们模拟可以写代码模拟
int main(int argc, char * argv[]) {
@autoreleasepool {
int retVal = 0;
do {
// 睡眠中等待消息
int message = sleep_and_wait();
// 处理消息
retVal = process_message(message);
} while (0 == retVal);
return 0;
}
}
有了RunLoop,程序并不会马上退出,而是保持运行状态。RunLoop可以处理App中的各种事件(比如触摸事件、定时器事件等);RunLoop可以在该做事的时候做事,该休息的时候休息,从而节约CPU资源,提高程序性能。
iOS中提供两套API来访问和使用RunLoop,分别为Foundation框架下面的NSRunLoop和Core Foundation下面的CFRunLoopRef。
NSRunLoop和CFRunLoopRef都是RunLoop对象,其中NSRunLoop是基于CFRunLoopRef的一层OC包装(参考图NSRunLoop是基于CFRunLoopRef的一层OC包装.png
),CFRunLoopRef是开源的,开源地址。
上图中,touch事件在主线程中触发,主线程所对应的RunLoop为mainRunLoop,通过OC与C语言的API访问RunLoop地址,可见两者指针不同,但是NSLog(@"%@",[NSRunLoop currentRunLoop]);输出的RunLoop详细信息发现NSRunLoop本质也是一个CFRunLoop,并且其地址与CFRunLoopGetMain()相同,进而可以说明NSRunLoop是基于CFRunLoopRef的一层OC包装。
2. RunLoop与线程的关系
平时都是通过:获取当前线程CFRunLoopGetCurrent();
,获取主线程CFRunLoopGetMain();
.下面我们就来分析一下这两个函数都做了什么,我们可以通过CFRunLoop源码来看一下.
CFRunLoopRef 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;
}
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
在调用CFRunLoopGetCurrent(),CFRunLoopGetMain()
函数时,函数内部都会调用_CFRunLoopGet0(pthread_t t)
函数,并传入不同的线程作为参数。可见调用CFRunLoopGetMain()
时,传入的是主线程,调用CFRunLoopGetCurrent()
时传入的是当前的线程(子线程或主线程)。接下来继续看看_CFRunLoopGet0(pthread_t t)
函数:
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
透过_CFRunLoopGet0(pthread_t t)
源码,如果__CFRunLoops不存在,系统就会利用当前的主线程"pthread_main_thread_np()
"创建一个mainLoop,也就是“mainRunLoop
”,然后将创建RunLoop的线程作为key,RunLoop作为value添加到一个全局的字典CFDictionarySetValue
中。最后获取"pthread_t
"线程对应的loop并返回loop。如果"pthread_t
"不是主线程,用同样的方法创建一个新的RunLoop,即"newLoop",同样将"newLoop"存放到字典中然后返回newLoop。所以关于RunLoop与线程的关系可以总结如下:
- 每条线程都有唯一的一个与之对应的RunLoop对象;
- RunLoop保存在一个全局的Dictionary里 ,线程作为key , Runloop作为value;
- 线程刚创建时并没有RunLoop对象, RunLoop会在第一次获取它时创建;
- RunLogp会在线程结束时销毁;
- 主线程的RunLoop已经自动获取(创建) ,子线程默认没有开启Runloop。
3. RunLoop对象结构
获取RunLoop对象
//Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
//Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
我们可以用NSLog(@"%@",[NSRunLoop currentRunLoop]);
方法打印Runloop对象,打印出RunLoop大概包含current mode、common modes、common mode items、modes这些信息。实际上从开源的文档里,RunLoop包含的成员远不止上面这些,具体包含内容如下图:
其中比较重要的是"_modes"这个成员,它是一个集合类型,里面存放了多个CFRunLoopMode类型的mode成员,一个RunLoop可以有多个mode,这些mode都包含在"_modes"里。CFRunLoopMode结构体如下:
imageCFRunLoopModeRef的定义
- CFRunLoopModeRef代表RunLoop的运行模式;
- 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer;
- RunLoop启动时只能选择其中一个Mode,作为currentMode
如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入:
①为什么要先退出再进入;
②不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响;- 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
4. CFRunLoopModeRef的常用的两种model
kCFRunLoopDefaultMode(NSDefaultRunLoopMode)
:App的默认Mode,通常主线程是在这个Mode下运行;
UITrackingRunLoopMode
:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响;
其实还有一种特殊的模式kCFRunLoopCommonModes 的模式NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式 NSRunLoopCommonModes并不是一个真的模式,它只是一个标记;
每一种mode里面都有source0、source1、observers、timers,他们主要负责的内容如下:
- Source0
触摸事件处理
performSelector:onThread:- Source1
基于Port的线程间通信
系统事件捕捉- Timers
NSTimer
performSelector:withobiect:afterDelay;- Observers
用于监听Runloop的状态
UI刷新( BeforeWaiting)
Autorelease pool ( BeforeWaiting )
两种方式监听RunLoop状态:
方式①:
- (void)viewDidLoad {
[super viewDidLoad];
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
switch (activity) {
case kCFRunLoopEntry: {
NSLog(@"kCFRunLoopEntry - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopBeforeTimers:{
NSLog(@"kCFRunLoopBeforeTimers - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopBeforeSources:{
NSLog(@"kCFRunLoopBeforeSources - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopBeforeWaiting:{
NSLog(@"kCFRunLoopBeforeWaiting - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopAfterWaiting:{
NSLog(@"kCFRunLoopAfterWaiting - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit: {
NSLog(@"kCFRunLoopExit - %@", mode);
CFRelease(mode);
break;
}
default:
break;
}
});
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
}
方式②:
void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry - %@", mode);
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers - %@", mode);
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources - %@", mode);
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting - %@", mode);
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting - %@", mode);
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit - %@", mode);
break;
default:
break;
}
CFRelease(mode);
}
- (void)viewDidLoad {
[super viewDidLoad];
// kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
}
CFRunLoopObserverRef的几种状态
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), //即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), //即将推出Loop
KCFRunLoopAllActivities = 0x0FFFFFFU
}
网友评论