#if __HAS_DISPATCH__// 使用 didDispatchPortLastTime 变量配合检查有没有 main queue 需要执行的if(MACH_PORT_NULL!=dispatchPort&&!didDispatchPortLastTime){msg=(mach_msg_header_t*)msg_buffer;if(__CFRunLoopServiceMachPort(dispatchPort,&msg,sizeof(msg_buffer),&livePort,0,&voucherState,NULL)){gotohandle_msg;// 执行 main queue 并将 didDispatchPortLastTime 设为 true}}#endifdidDispatchPortLastTime=false;...
上面代码是 main queue 可能被本次 RunLoop 执行的一个机会,可以看到 if 语句里还加入了 didDispatchPortLastTime 这个变量,该变量作用很像是获取上次 RunLoop 有没有执行过 main queue 的标志,假如 handle_msg 执行了 main queue, didDispatchPortLastTime 会被设为 true,这样在下次 RunLoop ,!didDispatchPortLastTime 为 false,不会直接跳转执行 handle_msg。
但假如 handle_msg 执行了其他的分支(比如 timer),那么本次 RunLoop 将不再执行 main queue 了(即便有),来到下次 RunLoop 时,由于!didDispatchPortLastTime 为 true,如果有 main queue 的代码要执行,就会直接跳转到 handle_msg 处理 main queue,略过 RunLoop 休眠等代码。
为此我通过简单的代码配合 CoreFoundation 源码来验证。
我在 timer 的回调里添加了执行 main queue 的方法,这么做的原因是想模拟下本次 RunLoop 没有执行 main queue 的情况。运行代码,timer 的回调执行完之后就进入到下一个 RunLoop 了,接着和预期的一致,来到了 goto handle_msg; 直接跳转去执行 main queue。
对于第一个疑惑,可以看出系统之所以这么做是为了确保加进来的 main queue 能获得快速执行和跳过界面更新和休眠提升效率。
利用 RunLoop 实现卡顿检测的原因
接下来 RunLoop 准备休眠
1234567891011121314
// 通知 RunLoop 的线程即将进入休眠if(!poll&&(rlm->_observerMask&kCFRunLoopBeforeWaiting))__CFRunLoopDoObservers(rl,rlm,kCFRunLoopBeforeWaiting);__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.#if __HAS_DISPATCH____CFPortSetInsert(dispatchPort,waitSet);#endif__CFRunLoopModeUnlock(rlm);__CFRunLoopUnlock(rl);
/** 这个 do-while 并不是死循环,__CFRunLoopServiceMachPort 是实际上使线程休眠的函数 在 __CFRunLoopServiceMachPort 函数内部调用 mach_msg(...) 实现休眠 USE_DISPATCH_SOURCE_FOR_TIMERS 在 iOS 系统上为 0*/#if USE_DISPATCH_SOURCE_FOR_TIMERSdo{msg=(mach_msg_header_t*)msg_buffer;__CFRunLoopServiceMachPort(waitSet,&msg,sizeof(msg_buffer),&livePort,poll?0:TIMEOUT_INFINITY,&voucherState,&voucherCopy);if(modeQueuePort!=MACH_PORT_NULL&&livePort==modeQueuePort){// 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));if(rlm->_timerFired){// Leave livePort as the queue port, and service timers belowrlm->_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);#elsemsg=(mach_msg_header_t*)msg_buffer;__CFRunLoopServiceMachPort(waitSet,&msg,sizeof(msg_buffer),&livePort,poll?0:TIMEOUT_INFINITY,&voucherState,&voucherCopy);#endif
handle_msg:;__CFRunLoopSetIgnoreWakeUps(rl);if(MACH_PORT_NULL==livePort){CFRUNLOOP_WAKEUP_FOR_NOTHING();// handle nothing}elseif(livePort==rl->_wakeUpPort){CFRUNLOOP_WAKEUP_FOR_WAKEUP();// do nothing on Mac OS}#if USE_DISPATCH_SOURCE_FOR_TIMERSelseif(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_TOOelseif(rlm->_timerPort!=MACH_PORT_NULL&&livePort==rlm->_timerPort){// 处理 NSTimer 或 CFRunLoopTimer 的回调CFRUNLOOP_WAKEUP_FOR_TIMER();if(!__CFRunLoopDoTimers(rl,rlm,mach_absolute_time())){// Re-arm the next timer__CFArmNextTimerInMode(rlm,rl);}}#endif#if __HAS_DISPATCH__elseif(livePort==dispatchPort){// 执行 main queue 的回调CFRUNLOOP_WAKEUP_FOR_DISPATCH();__CFRunLoopModeUnlock(rlm);__CFRunLoopUnlock(rl);_CFSetTSD(__CFTSDKeyIsInGCDMainQ,(void*)6,NULL);__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);_CFSetTSD(__CFTSDKeyIsInGCDMainQ,(void*)0,NULL);__CFRunLoopLock(rl);__CFRunLoopModeLock(rlm);sourceHandledThisLoop=true;didDispatchPortLastTime=true;}#endifelse{// 处理 source1 事件CFRUNLOOP_WAKEUP_FOR_SOURCE();// Despite the name, this works for windows handles as wellCFRunLoopSourceRefrls=__CFRunLoopModeFindSourceForMachPort(rl,rlm,livePort);if(rls){#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINImach_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);}#endif}}
#if USE_DISPATCH_SOURCE_FOR_TIMERSelseif(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