不知道大家有没有这个疑问,创建一个空工程运行起来,即使里面没有其它业务代码,App也不会退出,这是为什么?其实是因为Runloop的关系。那么Runloop到底是什么,又是怎么工作的呢?接下来我们将揭开它神秘的面纱。
案例分析
无处不在的Runloop
-
在
Runloop的 官方文档 中,我们可以看到Runloop是一个死循环模型,线程在执行完任务后会进行休眠,有新的任务需要执行时就会被唤醒。如下图所示:
可以这样理解这个图,做一些相应的操作后,Runloop会转变成相应的事件去处理。 -
代码分析:
- (void)sourceDemo{ // 1. __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"天王盖地虎"); }]; // 2. __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"hello word"); }); // 3. __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ void (^block)(void) = ^{ NSLog(@"123"); }; block(); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 4. __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ NSLog(@"来了,老弟!!!"); }下面拿第一种(
timer)案例来打印分析:从打印堆栈可以得出以下结论:
-
timer事件,在runloop对应的是__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__类型
-
Foundation中的Runloop,在底层实际对应的是CoreFoundation中的CFRunloop
-
-
一共有
6种类型的runloop事件:-
block应用:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
-
timer应用:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
-
响应source0:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
-
响应source1:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
-
GCD主队列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
-
观察源(observer):__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
-
与其他循环的区别
-
一般循环的
cpu:int main(int argc, char * argv[]) { @autoreleasepool { while (1) { NSLog(@"hello"); } return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));; } }运行后发现占用
30%左右的cpu -
Runloop所占cpu:运行一个空的工程,发现
cpu始终为0,但工程一直在运行
Runloop总结:
1. 保证程序的持续运行
2. 处理APP中的各种事件(触摸、定时器、performSelector)
3. 节省cpu资源、提供程序的性能:该做事就做事,该休息就息
Runloop到底是不是死循环,是怎样的一个循环呢?接下来我们走进源码的世界
源码解读
-
在上面的堆栈分析,我们可以得出如下的关系:
Runloop在底层是CFRunloop,解析来我们将对CFRunloop进行分析
-
在
Source Browser中下载 CoreFoundation源码 ,然后对CFRunloop.c对`文件进行阅读
创建
-
在源码中有一个
CFRunLoopRun方法:void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; do { result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); }- 主要是通过判断
result的结果进行do-while循环,说明这里是runloop创建的过程
- 主要是通过判断
-
先来看看
CFRunLoopGetCurrent,它是获得CFRunLoopRef的函数:CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; return _CFRunLoopGet0(pthread_self()); }- 这里先根据类型从
TSD获取rl,如果存在则返回,如果不存在则根据当前线程从_CFRunLoopGet0函数获取
- 这里先根据类型从
-
_CFRunLoopGet0:- 获取
CFRunLoopRef的过程比较简单:-
- 先判断当前线程,如果不存在则设置为主线程
-
- 判断
CFMutableDictionaryRef是否存在:
- 不存在:创建
CFMutableDictionaryRef,然后在主线程创建CFRunLoopRef,然后以主线程为key,mainloop为value存入字典 - 存在:根据当前线程获取
CFRunLoopRef,如果没有获取到,说明当前线程不是主线程,则根据当前线程创建,并以当前线程为key,newLoop为value存入字典,
- 判断
-
-
- 得到
loop后调用_CFSetTSD进行存储
- 得到
- 获取
-
获取
NSRunLoopRef的流程如下: -
从上面分析得知,无论是不是在主线程都需要创建
NSRunloopRef,那么这个runloop是个怎样的数据结构,接下来我们继续分析。
数据结构
-
进行
__CFRunLoopCreate函数,可以看到创建和赋值过程:-
loop的成员中有model,有item等,他们是什么类型不知道,点进去查看: -
这个我们知道
CFRunloop是个结构体,_commonModes,_commonModeItems和_modes是集合类型,再来看看_currentMode:
-
这里我们可以知道
CFRunLoopModeRef是个CFRunLoopMode结构体,并且看到了比较熟悉的字眼:sources0,sources1,obervers,timers,他们是集合或者是数组类型,也就是说Runloop有很多个CFRunLoopMode,每个model对应多个事件,如下图所示:
-
-
现在我们知道
items里面有很多的(source0,source1,timer,observer),他们是什么不知道,又是怎样依赖model在runloop里执行?接下来在runloop的底层进行探索分析
底层原理
-
我们通常在使用
timer时,通常会加入runloop,我们先从加入runloop开始入手分析,在CFRunLoop.h可以看到有add的几个类型:add的mode有以下几种类型:commonMode类型:CFRunLoopAddCommonModeblock类型:CFRunLoopPerformBlockSource类型:CFRunLoopAddSourceObserver类型:CFRunLoopAddObserverTimer类型:CFRunLoopAddTimer
1. 添加事件
-
CFRunLoopPerformBlock代码如下:runloop添加事件时主要做了以下几个步骤:-
- 确保
model存在
- 确保
-
- 创建
runloop中_block_item单向链表
- 创建
-
- 对
new_item相关参数进行赋值:
- 如果尾结点不存在,说明还没有添加
mode,则将头结点设置为new_item; - 如果尾结点存在,则设置尾结点
next为new_item - 最后再设置尾结点为
new_item
- 对
-
-
CFRunLoopAddCommonMode类型:-
此处函数的核心是
__CFRunLoopAddItemsToCommonMode,它的代码如下:
原来commonMode类型对应的是source、observer和timer三种事件,下面将对他们进行一一分析
-
-
CFRunLoopAddTimer类型:- 添加
timer类型的mode比较简单,主要有以下几个步骤:-
- 获取相应
modeName的mode
- 获取相应
-
- 确保
mode中的timers数组存在
- 确保
-
- 如果
timer的rlModes数组中没有新mode的name,则往rlModes中添加name
- 如果
-
- 将
timer存入对应model的timers数组
- 将
-
- 添加
-
CFRunLoopAddSource类型:source类型添加mode与timer类型类似:-
- 根据
modeName获取相应的mode
- 根据
-
- 将
source赋值给mode中的sources0或者sources1
- 将
-
- 将
runloop添加到source中的runLoops中
- 将
-
-
CFRunLoopAddObserver类型:- 添加
Observer类型的mode也和上面的类似:-
- 根据
modeName获取相应的mode
- 根据
-
- 在
mode的observers数组从后往前遍历:
- 如果遍历的
observer->order小于等于当前observer->order,则将observer插入到下当前index+1处 - 如果遍历完,
observer->order都小于数组里的observer->order,则将新的observer插入到数组的第一个位置
- 在
-
- 添加
从分析几个添加mode的过程,可以感知commonMode中的三个mode过程比较类似,block类型mode有所不同,它是以单向链表的形式添加的。
2. 进入循环
-
添加完
mode后将进行runloopRun,也就是调用函数CFRunLoopRunSpecific,源码如下:-
- 函数里先根据
modeName获取本次运行的mode,如果没有找到则不进入循环
- 函数里先根据
-
- 将
runloop的currentMode设置成本次运行的mode
- 将
-
- 如果
mode类型为entry,则observer通知Runloop即将进入loop
- 如果
-
- 调用
__CFRunLoopRun函数进行run
- 调用
-
- 如果
model类型为exit,则observer通知runloop即将退出
- 如果
-
-
__CFRunLoopRun函数主要代码如下:
主要的是根据状态判断循环条件,然后在循环中处理睡眠及相应事件,核心流程如下:
3. 事件处理
-
observers事件:
- 处理
observers事件,主要有以下几个步骤:-
- 获取
observers数组大小
- 获取
-
- 根据大小获取或者创建一个
CFRunLoopObserverRef类型的collectedObservers
- 根据大小获取或者创建一个
-
- 遍历
mode->observers并对collectedObservers进行相关赋值
- 遍历
-
- 遍历新的集合并调用
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__执行事件处理 它的实现如下:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { if (func) { func(observer, activity, info); } asm __volatile__(""); // thwart tail-call optimization } - 遍历新的集合并调用
-
-
blocks事件:
block的mode在runloop中是以链表的形式存在,它的执行过程如下:-
- 获取链表的头结点和尾结点
-
- 从头结点往尾结点方向遍历
-
- 如果满足条件,则执行
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__函数,进而执行block回调 它的核心函数如下:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void)) { if (block) { block(); } asm __volatile__(""); // thwart tail-call optimization } - 如果满足条件,则执行
-
-
timer事件:
执行timer事件先遍历mode的timers数组,将满足条件的timer放入新数组,然后遍历执行__CFRunLoopDoTimer函数:
__CFRunLoopDoTimer函数在满足条件下,执行__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__函数,进而执行回调 -
mainQueue事件
mainQueue事件是在runloop被唤醒后,然后调用__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__函数,进而调用_dispatch_main_queue_callback_4CF函数处理msg -
source0事件:
source0事件的处理需要判断类型:- 如果是
ID类型,则在相应的判断后执行__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__函数 - 如果是数组类型,则需要遍历数组,得到的
observer在满足条件下执行__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__函数
-
source1事件:source1的类型要简单的多:
当observer存在,则调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__函数进而执行回调。