前言
之前一直不知道什么是runloop,开发中也没怎么用到,直到最近开发的一个项目,同事在写计时的时候用NSTimer,在哪里边用到了runloop,才关注了一下。然后查了好多资料,看了好多博客把自己的学习所得记录下来,有什么不足或者错误地方,请大家多多指正,互相学习。
Runloop 是什么?
Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.
翻译:Run loops是线程的基础架构部分。一个runloop就是一个事件处理循环,用来不停的调配工作以及处理输入事件。使用runloop的目的是使你的线程在有工作的时候工作,没有的时候休眠。
Runloop基本作用
1.保持程序持续运行
2.处理程序中的各种事件(例如:触摸事件、定时器事件(NSTimer)、选择器事件(selecter))等等.
2.3 节省CPU资源,提高程序性能。runloop能让主线程有事情处理事情,没事情时候,主线程处于休眠状态.
……..
假如没有runloop,我们程序执行完后,就会立马退出。打开xcode,创建一个空的项目,找到main.m文件,把程序入口main()函数改成如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"runloop over");
return 0;
}
上面这段修改后的main()函数,我们再来运行程序, 程序运行到return 0,就立马结束了。与之相对应的,xcode模拟器也就关闭了界面。
Runloop 核心实现
runloop核心代码实现大致如下
int main(int argc, char * argv[]) {
BOOL running = YES;
do{
//执行各种任务,处理各种事件
}while(running)
return 0;
}
关于runloop核心部分就是这样的一个do-while循环函数,程序开始后,就一直在do-while里边运行,不会执行到 return 0这一行代码;想详细了解这部分实现的朋友可以看看这篇博客,里边有关于这部分的详细说明。
main()函数中的runloop
在我们创建一个工程时候,每一个程序都以一个入口函数int main(int argc, char * argv[]),程序自动帮我们runloop所以程序能够持续运行,不会退出。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
其实在UIApplicationMain()函数内部就帮我们创建了一个runloop对象,所以UIApplicationMain()没有返回值,保持了我们程序的持续运行。有心的朋友可以在 UIApplicationMain()函数前面和后面都加上NSlog输出一下,你就会发现, UIApplicationMain()前面的NSlog会打印输出,而UIApplicationMain()函数后面的NSlog是不会打印输出的。UIApplicationMain()函数里边的runloop相当于一个死循环的循环,并且这个默认启动的runloop是跟主线程相关联的,处理主线程相关的各种事件。
Runloop对象
iOS中有两套API来访问和使用Runloop
- Foundation框架下(基于OC)
- NSRunLoop:是基于CFRunLoopRef的一层包装
- Foundation获取runloop
4. 获取当前的runloop 可以调用[NSRunLoop currentRunLoop];
5. 获取主线程的runloop 可以调用[NSRunLoop mainRunLoop];
- Core Foundation框架下(基于C语言)
- CFRunLoopRef
- Core Foundation获取runloop
4. 获取当前的runloop 可以调用CFRunLoopGetCurrent();
5. 获取主线程的runloop 可以调用CFRunLoopGetMain();;
runloop对象的创建
- 创建一个runloop对象是通过
[NSRunLoop currentRunLoop];
来创建的关于runloop底层实现具体可以看看_CFRunLoopGet()
函数内部实现。_CFRunLoopGet()函数实现源码如下(以下代码摘自这里):
`static CFMutableDictionaryRef loopsDic;
//访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}/// 直接从 Dictionary 里获取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));if (!loop) {
/// 取不到时,创建一个
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
/// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
_CFSetTSD(…, thread, loop, __CFFinalizeRunLoop);
}OSSpinLockUnLock(&loopsLock);
return loop;
}- 创建一个runloop对象是通过
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}`
从上面的代码可以看出,当你创建其他线程的runloop对象时候,首先自动帮忙先创建主线程的runloop,然后创建其他线程的runloop对象。runloop对象通过字典来存储,每一个key对应一个线程,一个线程对应一个runloop。
6. CFRunLoopRef 的代码是开源的,具体函数可以去这里下载
Runloop与线程
每条线程都有一个唯一的与之对应的runloop对象,主线程的Runloop对象已经自动创建好了,不用手动创建,就在UIApplicationMain()函数里边;子线程的需要手动去创建。runloop在第一次启动时候创建,在线程结束的时候自动销毁。
Runloop相关的类
Core Foundation中关于runloop的类有5个:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef:事件源(输入源)
CFRunLoopTimerRef:基于事件的触发器
CFRunLoopObserverRef
CFRunLoopModeRef :表示runloop的运行模式,runloop要跑起来,必须得定义一个model,这个model称作currentModel。一个runloop对象可以有多个model,这些model可以切换;每个model又包含多个 Source/Timer/Observer,如果要切换model,必须先退出当前的loop,在指定一个model重新进入。这样做的目的是为了区分不同model下的Source/Timer/Observer,让他们不相互影响。每一个runloop默认情况下启用默认的kCFRunLoopDefaultMode模式,
系统默认有5种model
- kCFRunLoopDefaultMode:App默认的model,通常主线程在这个默认model下运行
- UITrackingRunLoopMode:跟踪界面的model,用于ScrollView追踪、触摸、滑动保证界面滑动时不受其他model影响。
- kCFRunLoopCommonModes:占位用的model,不是真正的model
- UIInitializationRunLoopMode:刚启动app时候启用的第一个model,启动完就不再使用的model。
- GSEventReceiveRunLoopMode:接受系统内部事件的model,通常不用
kCFRunLoopCommonModes使用:
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runloopModesTest) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
NSLog(@"---%@----",[NSRunLoop currentRunLoop]);
在项目上运行以上代码,在输出日志里边,有这些输出从图片截取的输出可以很好的看出NSRunLoopCommonModes,就是kCFRunLoopDefaultMode模式和UITrackingRunLoopMode模式之间来回转换的一个桥梁。
- CFRunLoopSourceRef按照官方文档分类:
Port-Based Source:基于端口的事件源
Custom Input Source:自定义的事件源
Cocoa Perform Selecter Source CFRunLoopSourceRef按照函数调用栈分类:
Source0:
Source1:基于port的,通过内核和其他线程通信,接收、分发系统事件CFRunLoopObserverRef:观察者,能够监听runloop状态的改变。可以监听时间点有如下几个:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),//即将进入runloop
kCFRunLoopBeforeTimers = (1UL << 1),//即将处理Timers
kCFRunLoopBeforeSources = (1UL << 2),//即将处理Sources
kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//刚从休眠唤醒
kCFRunLoopExit = (1UL << 7),//即将退出runloop
kCFRunLoopAllActivities = 0x0FFFFFFFU//监听所有的runloop状态
};
由于水平有限,就写到这里了。有什么错误或者问题,请大家多多指正。后面几天会在我的git上陆续编写一些关于runloop简单实用的demo。有兴趣可以下载看看,更好理解runloop。
简单demo地址:https://github.com/hu1023186811/runloop-test.git
参考文献: