前言
- 在
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类型: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比较简单,主要有以下几个步骤:- 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;
}