前言
上一篇文章主要讲解了Runloop
的理论相关的知识,今天这篇文章主要从源码的角度来解析Runloop[
。CF-1153.18源码
源码解析
CFRunLoopRun
runloop
开始运行的时候调用CFRunLoopRun
方法
- 我们看到最外层是一个
do while
循环 - 循环内调用
CFRunLoopRunSpecific
方法,返回一个result - 如果状态为:
kCFRunLoopRunStopped
和kCFRunLoopRunFinished
,则退出runloop
运行
CFRunLoopRunSpecific
- 首先通过
__CFRunLoopFindMode
方法找到对应的mode
,如果没有就创建 - 如果找不到,就会直接退出,所以
runloop
必须依赖mode
来运行
- _per_run_data是runloop每一次run的时候的相关数据
- 通知观察者,进入runloop
- 调用_
_CFRunLoopRun
开始run - 通知观察者,退出runloop
真正的核心代码就是在__CFRunLoopRun
方法里面,下面重点看一下__CFRunLoopRun
__CFRunLoopRun
第一步:
- 获取系统启动后的CPU运行时间,用于控制超时时间
- 状态判断,如果mode是stop状态则不进入循环
第二步:
- mach端口,在内核中,消息在端口之间传递。 初始为0
- 判断是否是主线程&&当前roop是否是mainRunloop&&当前model是否是commonMode
- 如果满足以上条件:则给mach端口赋值为主线程收发消息的端口
第三步:
- mode赋值为dispatch端口_dispatch_runloop_root_queue_perform_4CF
- 用在MacOX上
第四步:使用GCD实现runloop超时功能
扫描二维码关注公众号,回复:
13155273 查看本文章
- seconds是设置的runloop超时时间,一般为1.0e10,所以不会超时
进入内层do while循环
这个里面的源码可以精简一下,因为有些不是在iOS平台上的
do {
// 消息缓冲区,用户缓存内核发的消息
uint8_t msg_buffer[3 * 1024];
//取所有需要监听的port
__CFPortSet waitSet = rlm->_portSet;
//设置RunLoop为可以被唤醒状态
__CFRunLoopUnsetIgnoreWakeUps(rl);
//1.通知 Observers: RunLoop 即将处理 Timer 回调。
if (rlm->_observerMask & kCFRunLoopBeforeTimers)
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//2。通知 Observers: RunLoop 即将触发 Source0 (非port) 回调
if (rlm->_observerMask & kCFRunLoopBeforeSources)
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 执行被加入的block
//外部通过调用CFRunLoopPerformBlock函数向当前runloop增加block。新增加的block保存咋runloop.blocks_head链表里。
//__CFRunLoopDoBlocks会遍历链表取出每一个block,如果block被指定执行的mode和当前的mode一致,则调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__执行之
__CFRunLoopDoBlocks(rl, rlm);
//RunLoop 触发 Source0 (非port) 回调
// __CFRunLoopDoSources0函数内部会调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__函数
//__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__函数会调用source0的perform回调函数,即rls->context.version0.perform
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
//处理了source0后再次处理blocks
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
//
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
didDispatchPortLastTime = false;
//如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
//通知oberver即将进入休眠状态
if(!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
//接收waitSet端口的消息
//等待接受 mach_port 的消息。线程将进入休眠
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// 计算线程沉睡的时长
rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
// runloop置为唤醒状态
__CFRunLoopUnsetSleeping(rl);
// 8. 通知 Observers: RunLoop对应的线程刚被唤醒。
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//收到处理的消息进行处理
handle_msg:;
// 忽略端口唤醒runloop,避免在处理source1时通过其他线程或进程唤醒runloop(保证线程安全)
__CFRunLoopSetIgnoreWakeUps(rl);
if(MACH_PORT_NULL == livePort){
// livePort为null则什么也不做
}else if(livePort == rl->_wakeUpPort){
// livePort为wakeUpPort则只需要简单的唤醒runloop(rl->_wakeUpPort是专门用来唤醒runloop的)
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
}else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort){
//如果是一个timerPort
// 如果一个 Timer 到时间了,触发这个Timer的回调
// __CFRunLoopDoTimers返回值代表是否处理了这个timer
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextTimerInMode(rlm, rl);
}
}else if(livePort == dispatchPort){
//如果是GCD port
//处理GCD通过port提交到主线程的事件
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}else{
// 处理source1事件(触发source1的回调)
//// runloop 触发source1的回调,__CFRunLoopDoSource1内部会调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
/// 执行加入到Loop的block
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource; // 4
} else if (timeout_context->termTSR < mach_absolute_time()) {
/// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut; // 3
} else if (__CFRunLoopIsStopped(rl)) {
/// 被外部调用者强制停止了
__CFRunLoopUnsetStopped(rl); // 2
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
// 调用了_CFRunLoopStopMode将mode停止了
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped; // 2
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished; // 1
}
}while(0 == retVal)
复制代码
Runloop回调处理
下面主要讲解Runloop真正做事情的处理方法,通过上面的源码我们发现Runloop真正做事的时候会调用下面的方法:
__CFRunLoopDoBlocks
__CFRunLoopDoTimers
__CFRunLoopDoSources0
__CFRunLoopDoSource1
__CFRunLoopDoObserver1
复制代码
__CFRunLoopDoBlocks
这个方法主要是处理Blocks回调,下面主要粘贴部分源码进行讲解
runloop
对象有一个_block_item
结构的链表,里面存储了当前runloop
相关的block
- 遍历链表,如果block被指定的model与当前runloop的mode一样则执行block
- 调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__执行block
__CFRunLoopDoTimers
- 遍历
runloop
对象Timesr
数组,把时间到期的加入到新数组 - 遍历数组执行
__CFRunLoopDoTimer
调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
,执行回调方法
__CFRunLoopDoSources0
source0
是App
内部事件,由App
自己管理的,像UIEvent
、CFSocket
都是source0
source0
不能主动触发,需要调用CFRunLoopWakeUp(runloop)
来唤醒RunLoop
,- 框架已经帮我们做好了这些调用,比如网络请求的回调、滑动触摸的回调
- 通过
rlm Mode
获取source0
,然后转成sources
, sources
可能只有一个,也有可能是一个数组,根据不同的情况执行。- 调用之前判断source时候
__CFRunLoopSourceIsSignaled
是否已经标记 - 调用
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION(rls->_context.version0.perform, rls->_context.version0.info)
执行回调,其中perform
是要执行的函数,info
是参数
__CFRunLoopDoSource1
- 由
RunLoop
和内核管理,Mach port
驱动,如CFMachPort
、CFMessagePort
。 source1
包含了一个mach_port
和一个回调(函数指针),被用于通过内核和其他线程相互发送消息- 能主动唤醒 RunLoop 的线程
调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
执行回调方法
调用 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm);其内部调用mach_msg()函数完成mach内核的状态改变,即使runloop休眠,然后调用source1
__CFRunLoopDoObservers
- 通过mode获取observers
- 遍历调用
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
方法
Runloop的应用
- Runloop起死回生
- Runloop检测卡顿
- NSTimer不准确
- 线程保活
下面提供了一个demo
Runloop