前言
- 在
Runloop
的 官方文档 中,我们可以看到Runloop
是一个死循环模型,线程在执行完
任务后会进行休眠
,有新的任务需要执行
时就会被唤醒
。如下图所示
runloop
事件类型
- 一共有
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__
-
与while
循环比较
- 一般while
int main(int argc, char * argv[]) {
@autoreleasepool {
while (1) {
NSLog(@"hello");
}
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));;
}
}
Runloop
所占cpu
:Runloop
总结:
- 保证程序的持续运行
- 处理APP中的各种事件(
触摸
、定时器
、performSelector
) - 节省
cpu
资源、提供程序的性能:该做事就做事,该休息就息
源码解读
- Runloop对应2套Api, 如下
Runloop
在底层是CFRunloop
,解析来我们将对CFRunloop
进行分析,即对CoreFoundation源码中CFRunloop.c
文件进行阅读
Runloop
的创建
CFRunLoopRun
方法
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
// - 主要是通过判断`result`的结果进行`do-while`循环,说明这里是`runloop`创建的过程
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
- 看看
CFRunLoopGetCurrent
,它是获得CFRunLoopRef
的函数
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
// 根据类型从`TSD`获取`rl`,如果存在则返回,如果不存在则根据当前线程从`_CFRunLoopGet0`函数获取
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
_CFRunLoopGet0
函数
- 获取
CFRunLoopRef
的过程比较简单:- 1.先判断当前线程,如果不存在则设置为主线程
- 2.判断
CFMutableDictionaryRef
是否存在:- 不存在:创建
CFMutableDictionaryRef
,然后在主线程
创建CFRunLoopRef
,然后以主线程为key
,mainloop
为value
存入字典 - 存在:根据当前线程获取
CFRunLoopRef
,如果没有获取到,说明当前线程不是主线程,则根据当前线程创建,并以当前线程为key
,newLoop
为value
存入字典,
- 不存在:创建
- 3.得到
loop
后调用_CFSetTSD
进行存储
- 流程图如下:
- 从上面分析得知,无论是不是在主线程都需要创建
NSRunloopRef
Runloop
的数据结构
- 查看
__CFRunLoopCreate
函数,可以看到创建和赋值过程:-
loop
的成员中有modes
,有item
等,他们是什么类型不知道,点进去查看: CFRunloop
是个结构体,_commonModes
,_commonModeItems
和_modes
是集合类型,再来看看_currentMode
:
- 这里我们可以知道
CFRunLoopModeRef
是个CFRunLoopMode
结构体,并且看到了比较熟悉的字眼:sources0
,sources1
,obervers
,timers
,他们是集合或者是数组类型,也就是说Runloop
有很多个CFRunLoopMode
,每个mode
对应多个事件,如下图所示:
items
里面有很多的(source0
,source1
,timer
,observer
),他们是什么不知道,又是怎样依赖model
在runloop
里执行?接下来在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
比较简单,主要有以下几个步骤:- 1.获取相应
modeName
的mode
- 2.确保
mode
中的timers
数组存在 - 3.如果
timer
的rlModes
数组中没有新mode
的name
,则往rlModes
中添加name
- 4.将
timer
存入对应model
的timers
数组
- 1.获取相应
-
CFRunLoopAddSource
类型:
source
类型添加mode
与timer
类型类似:- 1.根据
modeName
获取相应的mode
- 2.将
source
赋值给mode
中的sources0
或者sources1
- 3.将
runloop
添加到source
中的runLoops
中
- 1.根据
CFRunLoopAddObserver
类型:
- 添加
Observer
类型的mode
也和上面的类似:- 1.根据
modeName
获取相应的mode
- 2.在
mode
的observers
数组从后往前遍历:- 如果遍历的
observer->order
小于等于当前observer->order
,则将observer
插入到下当前index+1
处 - 如果遍历完,
observer->order
都小于数组里的observer->order
,则将新的observer
插入到数组的第一个位置 从分析几个添加mode
的过程,可以感知commonMode
中的三个mode
过程比较类似,block
类型mode
有所不同,它是以单向链表的形式添加的。
- 如果遍历的
- 1.根据
2. 进入循环
- 添加完
mode
后将进行runloopRun
,也就是调用函数CFRunLoopRunSpecific
,源码如下:
-
1.函数里先根据
modeName
获取本次运行的mode
,如果没有找到则不进入循环 -
2.将
runloop
的currentMode
设置成本次运行的mode
-
3.如果
mode
类型为entry
,则observer
通知Runloop
即将进入loop
-
4.调用
__CFRunLoopRun
函数进行run
-
5.如果
model
类型为exit
,则observer
通知runloop
即将退出 -
__CFRunLoopRun
函数主要代码如下:
主要的是根据状态判断循环条件,然后在循环中处理睡眠及相应事件,核心流程如下:
3. 事件处理
- 1.
observers
事件: - 处理
observers
事件,主要有以下几个步骤:- 1.获取
observers
数组大小 - 2.根据大小获取或者创建一个
CFRunLoopObserverRef
类型的collectedObservers
- 3.遍历
mode->observers
并对collectedObservers
进行相关赋值 - 4.遍历新的集合并调用
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
执行事件处理 - 实现如下
- 1.获取
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
}
- 2.
blocks
事件: block
的mode
在runloop
中是以链表的形式存在,它的执行过程如下:- 1.获取链表的头结点和尾结点
- 2.从头结点往尾结点方向遍历
- 3.如果满足条件,则执行
__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
}
-
3.
timer
事件:执行
timer
事件先遍历mode
的timers
数组,将满足条件的timer
放入新数组,然后遍历执行__CFRunLoopDoTimer
函数:__CFRunLoopDoTimer
函数在满足条件下,执行__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
函数,进而执行回调 -
4.
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__
函数
- 如果是
- 6.
source1
事件:当
observer
存在,则调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
函数进而执行回调。
一些小的细节
RunLoop
休眠的实现原理
涉及到两个态之间的切换
__CFRunLoopServiceMachPort
__CFRunLoopServiceMachPort
,该函数是操作 RunLoop 进入休眠的关键,通过设置 mach port 接收消息实现。设置 mach port 接收消息后,线程会暂时挂起进入休眠,等待 mach port 响应。- RunLoop 虽然包含了 Source0、Source1、Timers 各种监控对象,但实际上,所有这些监控对象的事件触发(唤醒 RunLoop 去响应事件)都是基于 mach port 实现的:
- Source0:通过 RunLoop 的
_wakeUpPort
唤醒 RunLoop; - Source1:通过 Source1 绑定的 mach port(Mode 的
_portToV1SourceMap
)唤醒 RunLoop; - Timers:通过 Mode 的
_timerPort
在恰当的时间点唤醒 RunLoop;
- Source0:通过 RunLoop 的
- 另外,RunLoop 对 GCD 主队列上执行的操作有“特殊照顾”。代码中
dispatchPort
实现的大致效果是:主线程 RunLoop 在被唤醒并处理完必要的监控对象事件后,会尝试性地从dispatchPort
中捞一下是否有需要处理的“GCD调度主线程消息”(这次调用__CFRunLoopServiceMachPort
不会使 RunLoop 进入休眠,因为 timeout 参数传了0
),如果有则livePort
会被置为dispatchPort
,并直接跳到handle_msg
的livePort == dispatchPort
逻辑分支中立即处理。这样处理的意图应该是:- 保证主线程 RunLoop 的监控对象回调中的“GCD调度主线程操作”可以及时响应;
- 在主线程已被唤醒时,可以及时响应来自其他子线程的“GCD调度主线程操作”;
- 总之就是保证“GCD调度主线程操作”的响应优先级。RunLoop 需要进入休眠时,
dispatchPort
也会被添加到waitSet
中并作为__CFRunLoopServiceMachPort
入参,这意味着,来自子线程的 dispatch asyn on main 的调用,也是可以唤醒主线程 RunLoop 的。 - 最后,再留意到代码中的
poll
变量,poll 这个单词在操作系统领域基本可以理解为轮询方式的多路复用机制。从代码看,当次 Loop 如果是 poll 模式时,不触发kCFRunLoopBeforeWaiting
和kCFRunLoopAfterWaiting
事件,也就是说这次 Loop 线程不会进入休眠。此时__CFRunLoopServiceMachPort
的 timeout 参数传0
,和处理dispatchPort
时是一样的。所以,RunLoop 中处理了 Resource0 的 Loop 是存在特殊性的,处理完 Resource0 后,RunLoop 会尝试性地从 Mode 的所有 mach port 中捞一下是否有需要处理的 mach port 事件,有则立即处理,没有则进入下一次 Loop。 __CFRunLoopServiceMachPort
的代码。一堆内核编程的代码,十分晦涩。其实只需要关注到其中的调用mach_msg
函数的那句代码,注意到入参是MACH_RCV_MSG
,说明这里是监听 mach port 接收消息而不是发送消息。
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
Boolean originalBuffer = true;
kern_return_t ret = KERN_SUCCESS;
for (;;) { /* In that sleep of death what nightmares may come ... */
mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
msg->msgh_bits = 0;
msg->msgh_local_port = port;
msg->msgh_remote_port = MACH_PORT_NULL;
msg->msgh_size = buffer_size;
msg->msgh_id = 0;
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
// 核心操作:调用mach_msg函数接收消息
ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
voucher_mach_msg_revert(*voucherState);
*voucherState = voucher_mach_msg_adopt(msg);
if (voucherCopy) {
if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
*voucherCopy = voucher_copy();
} else {
*voucherCopy = NULL;
}
}
CFRUNLOOP_WAKEUP(ret);
if (MACH_MSG_SUCCESS == ret) {
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
if (MACH_RCV_TIMED_OUT == ret) {
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
if (MACH_RCV_TOO_LARGE != ret) break;
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}