平时开发中我们接触的NSRunLoop比较多。但是,NSRunLoop的api不是线程安全的,所以在一个NSRunLoop中操作另一个NSRunLoop时要格外小心。其中CFRunLoopRef是线程安全的,而且NSRunLoop只是对CFRunLoop的封装。所以,我们可以通过分析CFRunLoop的源码工作原理,来了解NSRunLoop的原理。
苹果已经对CoreFoundation代码开源,我们可以找到CFRunLoop的源码来分析。源码在这里
CFRunLoop入口函数
void CFRunLoopRun(void)作为入口函数。这个函数较小贴一下源码。
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
可以看到,runloop在运行后,启动了一个do-while循环(以前没读到源码,面试这样说,面试官会一脸鄙夷,现在可以啪啪打脸了)。
然后循环是否退出需要看CFRunLoopRunSpecific函数返回值判断result是否等于 kCFRunLoopRunStopped或kCFRunLoopRunFinished 。这两个值是一个枚举类型,源码也贴一下。
typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
kCFRunLoopRunFinished = 1,
kCFRunLoopRunStopped = 2,
kCFRunLoopRunTimedOut = 3,
kCFRunLoopRunHandledSource = 4
};
所以,运行循环调用的函数是CFRunLoopRunSpecific
CFRunLoopRunSpecific函数
这个函数需要四个参数:
rl : 当前runloop对象。
modeName : 默认的运行模式名称。
seconds : 超时时间。超时时间设置为10的10次方,可以理解为没有处理完就不超时。
returnAfterSourceHandled : 处理完当前source0后是否结束。CFRunLoop传的false,意思是会继续处理其他source0。
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
然后我们进入这个函数看一下,它都做了哪些事情。贴一下简化后的关键代码
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
首先这个函数先判断了一下这个runloop是否在dealloc,如果是就直接返回了kCFRunLoopRunFinished,也就结束了上边提到的do-while循环。
然后根据runloop对象和mode名称获取当前的mode。判断当前的mode和kCFRunLoopEntry进行&计算条件成立,那么调用函数__CFRunLoopRun。
如果是mode和kCFRunLoopExit进行&计算条件成立,那么就通知观察者kCFRunLoopExit。结束循环。
接下来说一下最重要的函数__CFRunLoopRun。
__CFRunLoopRun函数
因为这个函数有三百多行,就不贴源代码了,还是贴一些精简后的关键代码。这是CFRunLoopRef进行消息处理的关键函数。这里去掉了端口处理、windows、disaptch_timer 支持等。
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
//定义了一个mach消息机制的变量
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks(rl, rlm);
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
goto handle_msg;
}
}
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//阻塞等待
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
CFRUNLOOP_WAKEUP_FOR_TIMER();
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
⚠️注意:第一行注释就显示出了CFRunloop是线程安全的。
1、首先通过__CFRunLoopIsStopped判断runloop是否是stop状态,成立返回kCFRunLoopRunStopped。
2、判断mode的状态位rlm->_stopped,判断runloop是否是stop状态,成立返回kCFRunLoopRunStopped。
3、进入do-while循环,while的循环条件是0 == retVal。retVal是在进入循环前定义的初始化为0的变量,在循环内赋值为mode的不同状态,退出循环。
4、接下来是循环内的处理过程:
- 通知观察者,准备处理Timers事件
- 通知观察者,准备处理source事件
- 处理非延迟的主线程调用
- 如果端口不为空,且有msg(source0事件),处理这个事件,如果有source1事件直接处理后转去处理消息goto handle_msg
- 通知observers,runloop即将进入休眠状态kCFRunLoopBeforeWaiting
- 进入休眠状态,阻塞等待,等待被timer或者source唤醒。
- 被timer或者source唤醒
- 通知监听者,runloop被唤醒kCFRunLoopAfterWaiting,开始处理事情
- 给retVal变量赋值,退出循环
其实这就差不多是CFRunLoop的主要操作事件流程了。当然日常开发中我们可能只是需要知道一些mode在不同情况下的处理或者实现一条常驻线程等用法即可。捋一遍源码还是能增加一些对内部实现过程的理解。