写在前面
本文主要是记录关于RunLoop的一些简单介绍。
RunLoop
基本认识
RunLoop:翻译过来叫运行时循环,指的是在程序运行过程中循环的做一些事情。
主要应用在:
- 定时器(Timer)、PerformSelector
- GCD
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoReleasePool
上面是我们一个iOS程序的入口main
函数,在UIApplicationMain
函数中会去创建主线程的RunLoop对象,它用来保证程序不退出从而保证程序的持续执行。
我们可以把RunLoop运行时循环理解成一个do - while
循环,伪代码如下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
int retVal = 0;
do {
// 1.在休眠中等待消息
// 2.如果有消息 处理消息
}while(retVal = 0);
return 0;
}
}
RunLoop的基本作用:
- 保证程序的持续运行
- 处理App中的各种事件(比如触摸事件、定时器等)
- 节省CPU资源,提高程序的性能:在有消息的时候处理消息 没有消息的时候休眠。
RunLoop对象
iOS中有两套API来访问和使用RunLoop:
- Foundation框架:
NSRunLoop
- Core Foundation框架:
CFRunLoopRef
(开源代码:https://opensource.apple.com/tarballs/CF/)
其中NSRunLoop
是基于CFRunLoopRef
的一层OC封装。
RunLoop与线程的关系
- 每条线程都有唯一的与之相对应的RunLoop对象
- RunLoop保存在一个全局的字典里面,线程为
key
,RunLoop为value
- 线程刚创建的时候并没有RunLoop对象,而是在第一次获取RunLoop对象时去创建
- RunLoop会在线程结束时销毁
- 主线程的RunLoop已经自动获取(创建),子线程的RunLoop默认是没有开启的
获取RunLoop对象
Foundation框架:
[NSRunLoop currentRunLoop]; // 获取当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获取主线程的RunLoop对象
Core Foundation框架:
CFRunLoopGetCurrent(); // 获取当前线程的RunLoop对象
CFRunLoopGetMain();// 获取主线程的RunLoop对象
RunLoop相关的类
Core Foundation框架中关于RunLoop的五个类:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
RunLoop
对象结构如下:struct __CFRunLoop { pthread_t _pthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; };
RunLoopMode
结构如下:struct __CFRunLoopMode { CFStringRef _name; CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; };
各个类之间的关系如下图:
RunLoop的运行模式
CFRunLoopModeRef
表示是RunLoop的运行模式,一个RunLoop可以有若干个Mode,每个Mode里面又包含Source0、Source1、Timer、Observer。
RunLoop在启动时只能选择其中的一个Mode作为CurrentMode。
如果需要切换Mode需要退出当前RunLoop重新选择一个Mode进入。
不同模式下的Source0/Source1/Timer/Observer能分隔开来,互不影响。
如果一个Mode中没有任何Source0/Source1/Timer/Observer,这个RunLoop会立马退出。
常见的Mode有两种:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
:App的默认Mode,通常主线程在这个Mode下运行。
UITrackingRunLoopMode
:界面跟踪Mode,ScrollView的滑动,保证界面滑动时不受其他的影响。
RunLoop的几种状态
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中被唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
如下可以监听RunLoop的所有状态:
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
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;
default:
break;
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
RunLoop的运行逻辑
1、 Source0
- 触摸事件处理
- performSelector:onThread:
2、Source1
- 基于Port的线程之间的通信
- 系统事件的捕捉
3、Timers
- NSTimer
- performSelector:withObject:afterDelay:
4、Observers
- 用于监听RunLoop的状态
- UI刷新(BeforeWaiting)
- AutoReleasePool(BeforeWaiting)
RunLoop的运行逻辑如下:
RunLoop在实际开发中的应用
- 控制线程的生命周期(线程保活)
- 解决NSTimer在滑动时停止工作的问题
- 监控应用卡顿
- 性能优化
RunLoop的源码查看
从上面看到:RunLoop的源码入口在
CFRunLoopRunSpecific
注意:
1、使用Foundation框架打印出来的主线程RunLoop和Core Foundation框架打印出来的主线程RunLoop地址值不一样,原因在于Foundation框架的RunLoop是对Core Foundation框架RunLoop的一层封装。
2、系统事件是通过Source1来捕捉,之后分发到Source0去处理的。
3、RunLoop在休眠之前会去释放自动释放池和刷新UI等。
4、线程阻塞和RunLoop休眠不一样:线程阻塞还是在执行代码 当前线程根本没有真的休眠 RunLoop休眠真的是休眠 没有执行代码 CPU不会为此分配资源 就会省电。
写在最后
关于RunLoop的一些基本介绍、各种模式以及它整个完整的运行逻辑就介绍到这里了,如有错误请多多指教,最后欢迎去我的个人技术博客逛逛。