不知道大家有没有这个疑问,创建一个空工程运行起来,即使里面没有其它业务代码,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
类型:CFRunLoopAddCommonMode
block
类型:CFRunLoopPerformBlock
Source
类型:CFRunLoopAddSource
Observer
类型:CFRunLoopAddObserver
Timer
类型: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__
函数进而执行回调。