接上篇
CFRunLoopAddObserver(公开的)
#pragma mark - 将源OB添加到runloop对应的模式里
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef rlo, CFStringRef modeName) {
CHECK_FOR_FORK();
CFRunLoopModeRef rlm;
if (__CFRunLoopIsDeallocating(rl)) return;
//这个条件判断, 表示ob只能有一个runloop占有
if (!__CFIsValid(rlo) || (NULL != rlo->_runLoop && rlo->_runLoop != rl)) return;
__CFRunLoopLock(rl);
//如果是common模式
if (modeName == kCFRunLoopCommonModes) {
//创建runloop _commonModes是有值, 并且有 default字符串的
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
//这里用了懒加载, 当初创建runloop的时候, 只有rl->_commonModes和modes(没有源)
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
//将当前的 ob 添加到 commonModeItems里, 注意到这里为止值是将源添加到了 单独的集合里, 并没有添加到 runloop的某个mode里
CFSetAddValue(rl->_commonModeItems, rlo);
//根据 mode的数据结构, 这里执行的效果应该是 将 当前模式名(字符串)添加到了 rl->_commonModes
if (NULL != set) {
CFTypeRef context[2] = {rl, rlo};
/* add new item to all common-modes */
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
} else {
//如果不是 common
//先去runloop根据name去找模式rlm,因为毕竟最后要添加到rlm里的
rlm = __CFRunLoopFindMode(rl, modeName, true);
//懒加载
if (NULL != rlm && NULL == rlm->_observers) {
rlm->_observers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
}
if (NULL != rlm && !CFArrayContainsValue(rlm->_observers, CFRangeMake(0, CFArrayGetCount(rlm->_observers)), rlo)) {
Boolean inserted = false;
/*
ob的结构理由个 order的优先级的东西, 在这里体现出来了,
从后往前遍历, 如果当前要插入的ob的优先级大就 插在当前的位置
同时说明了在 CALLING_OUT_OB 的时候 是从后往前遍历找到ob,执行回调的
*/
for (CFIndex idx = CFArrayGetCount(rlm->_observers); idx--; ) {
CFRunLoopObserverRef obs = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
if (obs->_order <= rlo->_order) {
CFArrayInsertValueAtIndex(rlm->_observers, idx + 1, rlo);
inserted = true;
break;
}
}
//如果for没有处理,说明优先级最第, 直接插在第0个,这样runloop唤醒 CALLING_OUT_OB的时候, 从后往前回调,保证是醉后一个
if (!inserted) {
CFArrayInsertValueAtIndex(rlm->_observers, 0, rlo);
}
//设置注册监听的状态
rlm->_observerMask |= rlo->_activities;
// rlcount++
__CFRunLoopObserverSchedule(rlo, rl, rlm);
}
if (NULL != rlm) {
__CFRunLoopModeUnlock(rlm);
}
}
__CFRunLoopUnlock(rl);
}
__CFRunLoopRun
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl,
CFRunLoopModeRef rlm,
CFTimeInterval seconds,
Boolean stopAfterHandle,
CFRunLoopModeRef previousMode) {
uint64_t startTSR = mach_absolute_time();
if (__CFRunLoopIsStopped(rl)) {
/*
判断 rl->_perRunData 的状态是否是停止
一般runloop伴随线程的生命周期, 如果runloop停掉, 一般代表线程死亡状态,但这要看apple怎么实现了
当然我觉得 手动stop的时候, CF应该销毁掉对应的RunLoop, 源码里只是标记 stop的标志位
但是想想线程死掉的时候, runLoop应该也已经死掉, 毕竟线程所有数据(TSD,之前一直提的)会销毁掉
在这里如果停止,标记后, 直接返回
这里简单说一下CFRunLoopStop的流程
当外界调用了CFRunLoopStop 会标记为stop的状态
然后CFRunLoopStop函数会调用CFRunLoopWakeUp,
CFRunLoopWakeUp内部会主动向 runloop的端口(rl->_wakeUpPort)发送一条信息
这样就唤醒了runloop
但要注意的是, 只有runloop启动后,调用CFRunLoopStop才有意义, 还有runloop的被CFRunLoopStop唤醒时, 代码入口并不是在这里, 是后面睡眠的地方
*/
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
/*
当前函数是私有函数, 并且只被CFRunLoopRunSpecific调用
传递过来的rlm是通过 modename 在当前runloop对象里寻找到的 mode(rlm)
rlm里存放着runloop最终要处理的源, 它的 stopped标记意味着不需被处理
所以这里如果发现不需要被处理, 就不启动, 直接返回
全局搜索 关于 rlm->_stopped 相关赋值的上下文
_stopped只在创建 mode的时候 会被初始化成false, 代表mode不应该停掉
只有在手动调用了_CFRunLoopStopMode的时候, 会被设置成true, 但是没有地方调用_CFRunLoopStopMode
*/
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
/*
这里解释一下 mach_port_name_t 本质是 unsigned int
一般涉及到 dispatch 基本上都是与系统调度相关
*/
mach_port_name_t dispatchPort = MACH_PORT_NULL;
/*
这个值是定义的宏 0
字面理解是 只 派发 基础的调用
但是所谓的基础调用 咱也不知道具体有哪些是基础的调用
*/
bool tflag0 = HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY; //0
//runloop的前一个模式
bool tflag1 = (previousMode == NULL);
/*
_CFGetTSD 的接口声明在 ForFoundationOnly.h中,实现在 CFUtilities.c中
文档对该函数的解释是 从预先分配的插槽中获取一些线程的特定数据
在当前.c的文件调用的_CFGetTSD, 参数都是枚举, 定义在CFInternal.h中(__CFTSDKeyIsInGCDMainQ = 4)
枚举中同时也出现了与 自动释放池相关的常量, 总之在这里该函数的作用就是获取 gcd_main_queue(主线程) 的数据
这里如果第一次进入到这里来(不考虑别的文件里的函数调用的设置问题),而且我搜索了这个CF的框架, 只有本.c文件利用到了__CFTSDKeyIsInGCDMainQ
get(__CFTSDKeyIsInGCDMainQ)代码之前, 并没有对应的set(__CFTSDKeyIsInGCDMainQ),所以 tflag2是true, 但不代表着别的地方没有set过
比如UIKit框架里可能设置过, 毕竟没有源码可以判断
*/
bool tflag2 = (_CFGetTSD(__CFTSDKeyIsInGCDMainQ) == 0);
//这里的 条件判断 说白了就只看 tflag2 是不是获取到了值, 如果没有取到值, 下面一行代码就是根据条件是否获取 端口
Boolean libdispatchQSafe = pthread_main_np() && ( (tflag0 && tflag1)/*false*/ || (!tflag0 && tflag2) );
/*
如果当前的runloop(rl)就是主线程
准备运行的模式名( rlm->name )只有被标记了 common 才会赋值dispatchPort, 当然defalt模式也可能会被标记成common,事实上的确被标记了
_dispatch_get_main_queue_port_4CF的实现不知道在什么地方, 但是不管了, 看名字意思应该是 返回的端口主要用来和GCDMainQ通信的
这里如果是主线程的时候一定会去获取 dispatchPort, 因为测试的时候, 在父函数CFRunLoopRunSpecific调用的时候, default已经被标记了common
dispatchPort是涉及到 异步GCDMainQ的回调
*/
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name))
dispatchPort = _dispatch_get_main_queue_port_4CF();
#if USE_DISPATCH_SOURCE_FOR_TIMERS
/*
创建runloop对象的时候, 在最后一步findMode的时候, 可能会创建mode,
然后如果创建mode,会创建一个理论上永远不会执行的定时器,modeQueuePort是当时创建的时候管理的端口
这个端口 主要用来和mode的定时器通信的, 当然端口的东西我不是很懂, 但是应该是这样
*/
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue) {
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
//超时定时器
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
/*
如果切换模式(CFRunLoopRunSpecific)的时候
传入的超时时间 seconds 是0, 设置相关的信息
*/
if (seconds <= 0.0) { // instant timeout
seconds = 0.0;
timeout_context->termTSR = 0ULL;
////超时的 操作回调
} else if (seconds <= TIMER_INTERVAL_LIMIT/*504911232.0*/) {
//超时定时器的回调
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
//超时时间是 从当前系统的时间startTSR + 超时时间
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
} else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
//开始进入循环
do {
//消息缓存区的大小
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
//消息头指针
mach_msg_header_t *msg = NULL;
//被唤醒的端口
mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
HANDLE livePort = NULL;
Boolean windowsMessageReceived = false;
#endif
/*
在创建模式的时候, 初始化过 模式rlm->_portset 在mac下实际就是一个端口
加上下面这个目前为止已经出现了4个端口了
> dispatchPort 用来回调GCDMainQ的block 可能被赋值 主线程runloop运行标记了common源的时候
> modeQueuePort 用来唤醒mode的定时器 有值
> livePort 用来被睡眠函数唤醒赋值的端口
> 当前要声明的端口 waitSet, 这个端口可能用于mode里源的通信
而且目前为止 端口 并没有注册给 mach
2个定时器
> 创建mode的时候 创建的GCD定时器, 同时关联的modeQueuePort, 主要是唤醒定时器, 将_timerFired设置为true
> 上面设置的超时定时器
*/
__CFPortSet waitSet = rlm->_portSet;
/*
当前函数是要启动runloop
这里设置 标记 0x0,
最开始创建 runloop对象的时候, 这个标记曾经被设置为 wake状态
*/
__CFRunLoopUnsetIgnoreWakeUps(rl);
/*
能到这里, 至少说明runloop里有源, 判断是否有源的函数, 是他的父函数
在父函数CFRunLoopRunSpecific里如果没有源是不会启动的
这里 第一次进入循环的时候, 会通知所有的观察者, runloop启动的状态
*/
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
/*
处理block, 内部会调用到 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__,
这些block, 是runloop的 block_head, 至于什么时候添加进去 搜索发现是执行了 CFRunLoopPerformBlock的时候
当然CFRunLoopPerformBlock是公开的接口
*/
__CFRunLoopDoBlocks(rl, rlm);
/*
处理源 source, 注意这里 是处理 sources0, 不是sources1, 这个source0每次loop都会执行, 不管端口类型
*/
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
//这里 处理完 sourc0并且CALLING_OUT后, 紧接着又处理了block, 难道是 Sources0的执行代码里添 有CFRunLoopPerformBlock的代码?
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
//sourceHandledThisLoop只要__CFRunLoopDoSources0调用了 CALLING_OUT,这个值就是true
//termTSR是上面说的 超时时间
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
/*
上面说过
如果当前的runloop(rl)就是主线程
准备运行的模式名( rlm->name )只有被标记了 common(即在runloop的commoModes里) 才会获取的端口(dispatchPort)
在这里多了一个条件 didDispatchPortLastTime, 上在while外定义的ture, 所以这里的循环if不会进入
所以这里也就是说 只有主线程的runloop会在 执行common标记的模式的时候 并且didDispatchPortLastTime=false的时候会进入这里
主线程的非common标记 模式也不会来这里
其他子线程 是不会进入到这里面的
简单说下结论, 这里是主线程runloop睡眠的地方, 但是第一次是不会进入到下面这个if条件, 原因:
1> didDispatchPortLastTime 在循环体外设置的是true, 所以这里肯定不能进来
这个if条件后面接着的代码立即设置了 didDispatchPortLastTime = false, 并且后继也进入了睡眠(端口是),也就表示着, runloop被唤醒后,
循环回到这里的if, 很可能会进入if监听dispatchPort端口(GCDMainQ), 但是很明显 可能会被执行很多次__CFRunLoopServiceMachPort!!!
*/
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
msg = (mach_msg_header_t *)msg_buffer;
//主线程睡眠的地方
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
/*
主线程的 没有被标记为common的其他模式
子线程的 runloop 会来到这里往下走
标记didDispatchPortLastTime, 可能使 主线程runloop 的睡眠在goto的那个地方
*/
didDispatchPortLastTime = false;
/*
这句代码明显通知 observer, runloop开始进入睡眠
但能不能调用要看 if的条件
poll 在上面解释了, 当__CFRunLoopDoSources0正确调用了 CALLING_OUT, 就是true, 那么这里就不会进入if
或者超时时间是0的时候, 也不会进入
总结 只有当 上面的__CFRunLoopDoSources0没有正确处理, 并且 设置的超时不是0, 才会通知注册了kCFRunLoopBeforeWaiting的监听者
*/
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//修改runloop的状态是睡眠, 既然是准备睡眠, 那么后面的代码肯定是 要进入睡眠的函数__CFRunLoopServiceMachPort
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)
// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.
/**
上面这3行注释是苹果的提醒
在此之后不要执行任何用户标注(在通知休眠之后)
后面3句不懂
*/
/*
再次声明 dispatchPort的初始化是有条件的
如果当前的runloop(rl)就是主线程
准备运行的模式名( rlm->name )只有被标记了 common, dispatchPort才会主动去获取值
dispatchPort获取值的代码是在循环开始之前(dispatchPort = MACH_PORT_NULL)
当前函数设置 dispatchPort值 的地方,还有一个地方就是 window平台下调用睡眠函数的时候, 函数内部可能会修改,
如果主线程 dispatchPort一定有值的, 原因在dispatchPort声明的地方已经分析的很清楚的
然后在睡眠之前, 注册端口__CFPortSetInsert
有个地方不明白, 这里是值传递, 而且dispatchPort是空的(我只是根据当前代码流程的分析)
下面有个地方 是从睡眠中醒来之后, livePort居然可能会被睡眠函数 修改为dispatchPort,
而且好像只有执行了 __CFPortSetInsert之后, 被__CFRunLoopServiceMachPort(argSet, ...)才会被唤醒
全局睡眠的地方有2个, 一个是监听dispatchPort(GCDMainQ)的, 一个是监听waitSet(唤醒runloop), 这个__CFPortSetInsert是不是
意味着不管当前阻塞在哪个 __CFRunLoopServiceMachPort的地方, 只要其中一个(dispatchPort和waitSet)有消息来了,都会响应吗
*/
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
//循环
do {
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
/*
对比上面的睡眠函数 __CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)
livePort 在进入循环的时候 设置的是null, 当mach接收到消息的时候, livePort 会被赋值(__CFRunLoopServiceMachPort),
所以消息的判断是 用 livePort来判断的
*/
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
/*
这里是上面一句代码 runloop唤醒之后 仅接着执行这里
modeQueuePort是 在循环体之前定义的, 上面说了, modeQueuePort是在创建runloop的步骤里(__CFRunLoopFindMode),
根据平台 USE_DISPATCH_SOURCE_FOR_TIMERS 创建的定时器时关联的相关的队列,
定时器主要处理的任务是 在无限远的时间 将模式的timer的状态设置为 true,至于为什么要那么做, 不是很懂
通过这的if的条件判断, livePort的 居然可能被(__CFRunLoopServiceMachPort)修改为modeQueuePort
分析下应该很明了,也就是说如果当时创建runloop对象的时候, 创建对应mode的时候,modeQueuePort也顺利关联了, 那么启动 runloop后
从睡眠醒来,那么 消息可能是 mode的 定时器发出的, 当然上面说了, 基本不可能, 因为这个定时器的时间是 无限远, 基本不会执行, 但是如果消息发送过来了,那么这里if的处理也是符合逻辑的,
清空队列
*/
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
//这是苹果的官方注释 清空 mode 的内部的队列, 如果其中一个callout块设置了timerFired标志,则断开并为计时器提供服务。
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
/*
代码来到这 说明 消息是 由 mode的 定时器发出, 上面一句代码 猜测是执行队列里所有的任务(因为有个 perform)
执行的某个任务可能会修改掉 mode的 定时器的状态(初始化的时候是false 详见__CFRunLoopFindMode)
这里发现只要 _timerFired为true,就设置为false
*/
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
#else
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
#endif
#elif DEPLOYMENT_TARGET_WINDOWS
// Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
/*
这4行是苹果的注释, 基本意思 每次 loop 都移除掉之前 __CFPortSetInsert()
前面__CFPortSetInsert(dispatchPort, waitSet), 这里移除掉,后面每次loop的时候都会又insert
*/
// Must remove the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced. Also, we don't want them left
// in there if this function returns.
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
// 这里开始是runloop醒过来了, 因为能到这里来 表示上面的while被唤醒, 那么这里就通知observers
__CFRunLoopUnsetSleeping(rl);
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//到这里来有2个地方, 1是上面的第一个睡眠的地方(当时说的 只有主线程的runloop,而且执行的模式是 common的时候, 睡眠被唤醒后直接goto过来)
// 2是顺着代码走下来
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
//window平台的处理
#if DEPLOYMENT_TARGET_WINDOWS
if (windowsMessageReceived) {
// These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
if (rlm->_msgPump) {
rlm->_msgPump();
} else {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
// To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
// Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
// NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
__CFRunLoopSetSleeping(rl);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopUnsetSleeping(rl);
// If we have a new live port then it will be handled below as normal
}
#endif
//睡眠唤醒后, livePort 为什么可能是空, 不懂
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
}
//睡眠唤醒后, 消息可能是 runloop的唤醒端口发出的, 居然什么也不做
else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
// Always reset the wake up port, or risk spinning forever
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
/*
如果端口是 mode的定时器发出的, 处理的是所有的定时器,
但是我不明白的是 mode的定时器设置的时间明明是遥远的未来
*/
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
//如果端口是 dispatchPort, 当然dispatchPort是主线程的时候一定会有值
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
void *msg = 0;
#endif
//处理runloop里的block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
//这里设置为true后, 下次的loop循环会跳过 goto那里, 不在那个地方睡眠
didDispatchPortLastTime = true;
}
else { //其他端口
//处理source1
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#elif DEPLOYMENT_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
}
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
__CFRunLoopDoBlocks(rl, rlm);
//处理任务后, runloop标记可能会被改掉, 这里设置对应的循环标记
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;
}
上面源代码的分析里, 关于睡眠函数__CFRunLoopServiceMachPort
,有2个地方, 我在上面注释的时候的理解是说只要注册了__CFPortSetInsert(dispatchPort, waitSet)
, 那么不管当前是睡眠在goto的那个地方,还是另一个地方(waitSet)
,只要有消息
, 那么对应的就会响应 eg:当前在goto
这里睡眠, 如果当前是 waitSet
有响应了, 那么代码就从waitSet
睡眠这里开始, 这只是我的推断, 从代码逻辑上来讲我觉得应该就是这样的, 当然我对端口的知识了解很少
图解
这张图和分析的地方, 出入很大, 我自己懒得画了
验证一些现象
CFRunLoopObserverRef
通过代码可以明显的知道, 系统开启Loop的代码在UIApplicationMain
以后, 如果将 observer
的监听设置在 viewDidLoad
里, 那么将不会监听到 kCFRunLoopEntry
, 下面测试代码
void ob_callback(CFRunLoopObserverRef ob, CFRunLoopActivity ac, void* info){
NSLog(@"%@", CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()));
switch (ac) {
case kCFRunLoopEntry:{
/**
程序启动的时候 会在CFRunLoopRunSpecific的时候, 执行一次
后面切换 runloop的模式的时候, 会再执行, 比如 defalut模式下的timer, 这个时候滑动scroll, 系统会切换模式, 有限执行scroll的滑动, 就会重新执行entry
*/
NSLog(@"entry runloop");
}break;
case kCFRunLoopBeforeTimers:{
//没有定时器也会执行, 因为只是通知
NSLog(@"h s t");
}break;
case kCFRunLoopBeforeSources:{
NSLog(@"h s b");
}break;
case kCFRunLoopBeforeWaiting:{
NSLog(@"will sleep");
}break;
case kCFRunLoopAfterWaiting:{
NSLog(@"wake up");
}break;
case kCFRunLoopExit:{
NSLog(@"exit");
}break;
default:
break;
}
}
- (void)viewDidLoad {
[super viewDidLoad];
#if 1
/**
ob创建的时候, 系统的runloop已经启动, 所以不能检测到 entity的状态, 所以ob的创建放在runloop启动前
entity状态是 CFRunLoopRunSpecific 里调用的
如果切换runloop的状态, 系统会重新调用 CFRunLoopRunSpecific
*/
CFRunLoopObserverRef ob = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, 1, 0, ob_callback, NULL);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), ob, kCFRunLoopCommonModes);
#endif
}
//打印结果
kCFRunLoopDefaultMode
h s t
kCFRunLoopDefaultMode
h s b
...
并没有 entry runloop
将注册的代码放到
load
里
+ (void)load{
CFRunLoopObserverRef ob = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, 1, 0, ob_callback, NULL);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), ob, (CFStringRef)kCFRunLoopCommonModes);
}
//打印
kCFRunLoopDefaultMode
entry runloop
kCFRunLoopDefaultMode
h s t
kCFRunLoopDefault
....
//很明显有
runloop切换模式的时候, 会先唤醒当前的模式, 然后停掉, 然后重新进入新的模式, 下面测试的是 滑动scroll的时候,监听的模式是Tracking,等到模拟器已经完全启动稳定后测试的
//因为监听的是 UITrackingRunLoopMode, 当前app稳定前并没有什么打印, 也说了app默认是default模式
//当滑动 textView的时候 打印如下:
2019-06-06 20:24:35.336139+0800 RunLoop[3906:2559470] UITrackingRunLoopMode
2019-06-06 20:24:35.336376+0800 RunLoop[3906:2559470] entry runloop
2019-06-06 20:24:35.336561+0800 RunLoop[3906:2559470] UITrackingRunLoopMode
2019-06-06 20:24:35.336692+0800 RunLoop[3906:2559470] h s t
...
2019-06-06 20:24:35.486334+0800 RunLoop[3906:2559470] UITrackingRunLoopMode
2019-06-06 20:24:35.486551+0800 RunLoop[3906:2559470] exit
//说明在滚动的时候, 模式切换为了 tracking, 而且没有任何关于defalut的打印, 经过分析也可以发现, 在entry runloop的打印之前, 肯定是exit defaut模式
//最后exit tracking的时候, 后面肯定是 entry runloopp default模式
UI点击事件
UI点击事件肯定是内核通过端口唤醒了app, 这个端口应该是上面分析的
其他端口
的情况
因为MACH_PORT_NULL == livePort
什么也不做
livePort == rl->_wakeUpPort
什么也不做
livePort == modeQueuePort
处理timers
livePort == dispatchPort
处理主线程的GCDMainQ
只剩下其他的情况,代码里处理的是source1, 但是最后打印的并不是soure1, 说明UI点击事件是source0, 只不过循环跳转到了前面, 通知了observer
,然后做了source0
, 也说明了点击按钮之前runloop的睡眠不是 goto的那个地方
UIclick.png
而对应先关的源代码截图
image.png
image.png
source0.png
image.png
这张图说明了 点击屏幕的时候runloop睡眠的地方不是
goto
那里, 如上面的4张图, 全文没有看到调用CFRunLoopSourceCreate
的代码,说明了source
的创建交给了外界, 系统创建了source0,唤醒了runloop, 我想最后处理完了之后将source0
移除掉了, 否则根据代码,上面source0.png的解释, 他不端口类型的控制, 那么不管什么唤醒了runloop, 都会执行source0
, 这怎么也不符合逻辑, 所以我认为,在执行完source0
后, 会将其移除runloop,当然这只是我猜测
异步线程gcdMainQ事件
如以下代码的测试
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
assert0:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
assert1:
NSLog(@"mainQ");
});
});
}
当断点在
assert0
的位置的时候, 函数调用栈:
和上面UI点击事件一样,
source0
的处理流程, 接下来断点走到assert1
, 调用栈如下:
调用栈里面明细 出现了
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
, 查看RunLoop.c
的文件, 出现__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
的地方只有livePort == dispatchPort
的时候, 这里将局部的代码再次贴出来
...
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
void *msg = 0;
#endif
//处理runloop里的block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
//这里设置为true后, 下次的loop循环的睡眠的地方在 goto那里
didDispatchPortLastTime = true;
}
这段代码不仅说明了, 在分析源代码的时候的猜测 ,
dispatchPort
端口是用来处理异步GCDMainQ
的回调唤醒, 观察这段代码还有一个容易忽略的地方, 这了修改了didDispatchPortLastTime
, 这样会导致runLoop
下次睡眠的地方跳过goto
那里
runloop的源的创建和添加都留给了外界, obsever,timer,source的规则都一样, 这简单给一个source的自定义demo,瞎写的
#import "ViewController.h"
#include <pthread.h>
static pthread_t subThread;
static pthread_mutex_t thread_lock;
static pthread_cond_t thread_cond;
static pthread_mutex_t source_lock;
static pthread_cond_t task_cond;
static CFRunLoopSourceRef mySource;
static CFRunLoopRef subRunLoop;
struct SourceInfo{
NSString* name;
}info;
#pragma mark - 初始化数据
static void init_data(){
pthread_mutex_init(&thread_lock, NULL);
pthread_mutex_init(&source_lock, NULL);
pthread_cond_init(&thread_cond, NULL);
pthread_cond_init(&task_cond, NULL);
}
#pragma mark - 创建线程
static void create_th(){
if (subThread) return;
pthread_mutex_lock(&thread_lock);
if (subThread) {
pthread_mutex_unlock(&thread_lock);
return;
}
void* THREAD_HANDLE(void*);
pthread_create(&subThread, NULL, THREAD_HANDLE, NULL);
pthread_mutex_unlock(&thread_lock);
}
#pragma mark - 销毁
__unused static void destory(void){
pthread_mutex_destroy(&thread_lock);
pthread_mutex_destroy(&source_lock);
pthread_cond_destroy(&thread_cond);
pthread_cond_destroy(&task_cond);
}
#pragma mark - 创建源
static void source_create(void){
if(mySource) return;
pthread_mutex_lock(&source_lock);
if (mySource){
pthread_mutex_unlock(&source_lock);
return;
}
void schedule(void*, CFRunLoopRef, CFRunLoopMode);
void cancel(void*, CFRunLoopRef, CFRunLoopMode);
void perform(void*);
info.name = @"刚创建还没注册";
///第2个参数 对应回调里的 info 这里传递的null
CFRunLoopSourceContext context = {0, &info, NULL, NULL, NULL, NULL, NULL,
schedule,
cancel,
perform};
mySource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
pthread_mutex_unlock(&source_lock);
}
#pragma mark - 创建源的回调
#pragma mark - 被注册到runloop mode的时候调用
void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
///其中的info 是创建 上下文的时候的第二个参数
struct SourceInfo* tmp = info;
printf("source加入到runloop的时候:%s %p %p \n",tmp->name.UTF8String,pthread_self(), subThread);
}
#pragma mark - 从runloop移除的时候调用
void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
struct SourceInfo* tmp = info;
printf("source从runloop移除的时候:%s %p %p \n",tmp->name.UTF8String,pthread_self(), subThread);
}
#pragma mark - 此次runloop的循环下 执行到源的时候调用
void perform(void *info){
///其中的info 是创建 上下文的时候的第二个参数
struct SourceInfo* tmp = info;
printf("runloop被source唤醒:%s %p %p \n",tmp->name.UTF8String,pthread_self(), subThread);
#if 0
int i = 11;
printf("looping begin\n");
for (;i;) {
printf("%d ",--i);
sleep(1);
}
printf("looping end\n");
#endif
pthread_cond_signal(&task_cond);
}
#pragma mark - 子线程
void* THREAD_HANDLE(void* arg){
loop_run:
/**
线程启动的时候 先不创建runloop, 因为没有任何源, 就在这里阻塞等待
一旦有人 发送信号之后, 立即创建 源将源添加到 runloop里, 启动runloop
*/
pthread_mutex_lock(&thread_lock);
if (mySource) {
subRunLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(subRunLoop, mySource, kCFRunLoopDefaultMode);
///如果来到这里 最好解锁
pthread_mutex_unlock(&thread_lock);
//启动runloop
CFRunLoopRun();
///表示runloop退出循环了
if (!CFRunLoopIsWaiting(subRunLoop)) {
pthread_mutex_lock(&thread_lock);
///否则一直等待
pthread_cond_wait(&thread_cond, &thread_lock);
pthread_mutex_unlock(&thread_lock);
goto loop_run;
}
}else{
///否则一直等待
pthread_cond_wait(&thread_cond, &thread_lock);
pthread_mutex_unlock(&thread_lock);
goto loop_run;
}
return NULL;
}
@interface ViewController ()
@property (nonatomic,strong) NSThread* thread;
@property (nonatomic) CFRunLoopSourceRef mySource;
@end
@implementation ViewController
@dynamic mySource;
+ (void)initialize{
init_data();
create_th();
}
- (IBAction)addSource{
///创建源
source_create();
///发送信号 添加源到runloop
pthread_cond_signal(&thread_cond);
}
#define info_lock(_code_) \
pthread_mutex_lock(&source_lock); \
(_code_) ;\
pthread_mutex_unlock(&source_lock);
- (IBAction)postmsg{
if (CFRunLoopIsWaiting(subRunLoop)) {
info_lock(info.name = @"zl");
///强制唤醒runloop 必须先标记 源(mySource), 然后唤醒runloop, runloop才会执行标记的源
CFRunLoopSourceSignal(mySource);
CFRunLoopWakeUp(subRunLoop);
}
}
- (IBAction)delSource{
if (subRunLoop && mySource) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
pthread_mutex_lock(&source_lock);
CFRunLoopRemoveSource(subRunLoop, mySource, kCFRunLoopDefaultMode);
CFRunLoopStop(subRunLoop);
pthread_mutex_unlock(&source_lock);
});
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
}
@end
RunLoop的应用
关于应用的东西, 网上一大坨, 这里写了也是和其他人一样的
结语
runloop还有很多东西很深的东西, 我么有时间也没有太大的精力去研究,上面的分析都是个人看法, 有不对的地方很正常, 我对端口的知识还是小白, 实在是没精力去搞