据苹果在文档里的说明,RunLoop 内部的逻辑大致如下:

/// 用DefaultMode启动
void CFRunLoopRun(void) {CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}/// 用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {/// 首先根据modeName找到对应modeCFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);/// 如果mode里没有source/timer/observer, 直接返回。if (__CFRunLoopModeIsEmpty(currentMode)) return;/// 1. 通知 Observers: RunLoop 即将进入 loop。__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);/// 内部函数,进入loop__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {Boolean sourceHandledThisLoop = NO;int retVal = 0;do {/// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);/// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);/// 执行被加入的block__CFRunLoopDoBlocks(runloop, currentMode);/// 4. RunLoop 触发 Source0 (非port) 回调。sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);/// 执行被加入的block__CFRunLoopDoBlocks(runloop, currentMode);/// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。if (__Source0DidDispatchPortLastTime) {Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)if (hasMsg) goto handle_msg;}/// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。if (!sourceHandledThisLoop) {__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);}/// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。/// ? 一个基于 port 的Source 的事件。/// ? 一个 Timer 到时间了/// ? RunLoop 自身的超时时间到了/// ? 被其他什么调用者手动唤醒__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg}/// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);/// 收到消息,处理消息。handle_msg:/// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。if (msg_is_timer) {__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())} /// 9.2 如果有dispatch到main_queue的block,执行block。else if (msg_is_dispatch) {__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);} /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件else {CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);if (sourceHandledThisLoop) {mach_msg(reply, MACH_SEND_MSG, reply);}}/// 执行加入到Loop的block__CFRunLoopDoBlocks(runloop, currentMode);if (sourceHandledThisLoop && stopAfterHandle) {/// 进入loop时参数说处理完事件就返回。retVal = kCFRunLoopRunHandledSource;} else if (timeout) {/// 超出传入参数标记的超时时间了retVal = kCFRunLoopRunTimedOut;} else if (__CFRunLoopIsStopped(runloop)) {/// 被外部调用者强制停止了retVal = kCFRunLoopRunStopped;} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {/// source/timer/observer一个都没有了retVal = kCFRunLoopRunFinished;}/// 如果没超时,mode里没空,loop也没被停止,那继续loop。} while (retVal == 0);}/// 10. 通知 Observers: RunLoop 即将退出。__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}

可以看到,实际上 RunLoop 就是这样一个函数,其内部是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。

CFRunLoopRef之系统默认注册的5个Mode:

CFRunLoop {current mode = kCFRunLoopDefaultModecommon modes = {UITrackingRunLoopModekCFRunLoopDefaultMode}common mode items = {// source0 (manual)CFRunLoopSource {order =-1, {callout = _UIApplicationHandleEventQueue}}CFRunLoopSource {order =-1, {callout = PurpleEventSignalCallback }}CFRunLoopSource {order = 0, {callout = FBSSerialQueueRunLoopSourceHandler}}// source1 (mach port)CFRunLoopSource {order = 0,  {port = 17923}}CFRunLoopSource {order = 0,  {port = 12039}}CFRunLoopSource {order = 0,  {port = 16647}}CFRunLoopSource {order =-1, {callout = PurpleEventCallback}}CFRunLoopSource {order = 0, {port = 2407,callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}CFRunLoopSource {order = 0, {port = 1c03,callout = __IOHIDEventSystemClientAvailabilityCallback}}CFRunLoopSource {order = 0, {port = 1b03,callout = __IOHIDEventSystemClientQueueCallback}}CFRunLoopSource {order = 1, {port = 1903,callout = __IOMIGMachPortPortCallback}}// OvserverCFRunLoopObserver {order = -2147483647, activities = 0x1, // Entrycallout = _wrapRunLoopWithAutoreleasePoolHandler}CFRunLoopObserver {order = 0, activities = 0x20,          // BeforeWaitingcallout = _UIGestureRecognizerUpdateObserver}CFRunLoopObserver {order = 1999000, activities = 0xa0,    // BeforeWaiting | Exitcallout = _afterCACommitHandler}CFRunLoopObserver {order = 2000000, activities = 0xa0,    // BeforeWaiting | Exitcallout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exitcallout = _wrapRunLoopWithAutoreleasePoolHandler}// TimerCFRunLoopTimer {firing = No, interval = 3.1536e+09, tolerance = 0,next fire date = 453098071 (-4421.76019 @ 96223387169499),callout = _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv (QuartzCore.framework)}},modes = {CFRunLoopMode  {sources0 =  { /* same as 'common mode items' */ },sources1 =  { /* same as 'common mode items' */ },observers = { /* same as 'common mode items' */ },timers =    { /* same as 'common mode items' */ },},CFRunLoopMode  {sources0 =  { /* same as 'common mode items' */ },sources1 =  { /* same as 'common mode items' */ },observers = { /* same as 'common mode items' */ },timers =    { /* same as 'common mode items' */ },},CFRunLoopMode  {sources0 = {CFRunLoopSource {order = 0, {callout = FBSSerialQueueRunLoopSourceHandler}}},sources1 = (null),observers = {CFRunLoopObserver >{activities = 0xa0, order = 2000000,callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv})},timers = (null),},
//CFRunLoopMode  {sources0 = {CFRunLoopSource {order = -1, {callout = PurpleEventSignalCallback}}},sources1 = {CFRunLoopSource {order = -1, {callout = PurpleEventCallback}}},observers = (null),timers = (null),},
//CFRunLoopMode  {sources0 = (null),sources1 = (null),observers = (null),timers = (null),}}
}

可以看到,系统默认注册了5个Mode:

  1. kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。

  2. UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。

  3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。

  4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。

  5. kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。

当 RunLoop 进行回调时,一般都是通过一个很长的函数调用出去 (call out), 当你在你的代码中下断点调试时,通常能在调用栈上看到这些函数。下面是这几个函数的整理版本,如果你在调用栈中看到这些长函数名,在这里查找一下就能定位到具体的调用地点了

{
// 1. 通知Observers,即将进入RunLoop
// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();
//  __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);do {
//
// 2. 通知 Observers: 即将触发 Timer 回调。
//      __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
//      __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
//      __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
//
// 4. 触发 Source0 (非基于port的) 回调。
//      __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
//      __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
///// 6. 通知Observers,即将进入休眠
// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
//      __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
//
// 7. sleep to wait msg.mach_msg() -> mach_msg_trap();
//
//
// 8. 通知Observers,线程被唤醒
//      __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
//
// 9. 如果是被Timer唤醒的,回调Timer
//  __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
//
// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
//    __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
//
// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
//    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
//
//} while (...);
//
// 10. 通知Observers,即将退出RunLoop
// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
//   __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}

AutoreleasePoolApp启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

事件响应苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。这个过程的详细情况可以参考这里。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。

手势识别当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

界面更新当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数:_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。

这个函数内部的调用栈大概是这样的:

_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()QuartzCore:CA::Transaction::observer_callback:CA::Transaction::commit();CA::Context::commit_transaction();CA::Layer::layout_and_display_if_needed();CA::Layer::layout_if_needed();[CALayer layoutSublayers];[UIView layoutSubviews];CA::Layer::display_if_needed();[CALayer display];[UIView drawRect];

CFRunLoopRef 的内部逻辑(向 ibireme学习)相关推荐

  1. ASP.NET Core分布式项目实战(Consent 确认逻辑实现)--学习笔记

    任务22:Consent 确认逻辑实现 接下来,我们会在上一节的基础上添加两个按钮,同意和不同意,点击之后会把请求 post 到 ConsentController 处理,如果同意会通过 return ...

  2. InCallContrller内部逻辑

    前言 刚刚开始看InCallContrller内部逻辑是会有点复杂的所以我今天来做一个笔记. 作用 : InCallContrller的作用就是为了调用InCallUi页面,InCallContrll ...

  3. 关于汽车仪表中车速表的内部逻辑

    车辆上车速表显示车速和实际车速 我们从车速表上看到的速度,其实是仪表想让驾驶员看到的"假"速度(GB 15082中称为指示车速),它不是车辆当前的"真"速度(G ...

  4. zynq7000 学习(二十四)VGA 接口原理分析和控制逻辑的实现学习

    VGA 接口原理分析和控制逻辑的实现学习 学习内容 本课程首先 进行 VGA 接口 的原理介绍,然后 编写逻辑 控制代码,最终进行 仿真和实现. 本课程 主要包括原理介绍和 设计实现 两个部分. 原理 ...

  5. 阅读 | 《逻辑工作法》学习笔记

    <逻辑工作法>学习笔记 PART1 避免被上司批评的思维方式 PART2 科学安排时间,避免无意义的加班 PART3 让身边人刮目相看的思维方式 PART4 流畅顺利地编写资料的写作技巧 ...

  6. 【教程】手把手教你如何利用工具(IE9的F12)去分析模拟登陆网站(百度首页)的内部逻辑过程

    声明:本文章转载自crifan的技术人生 [教程]手把手教你如何利用工具(IE9的F12)去分析模拟登陆网站(百度首页)的内部逻辑过程 重要提示: 1.此贴,以后不再更新: 2.想要看更新的内容,请移 ...

  7. Storm通信机制,Worker进程间通信,Worker进程间通信分析,Worker进程间技术(Netty、ZeroMQ),Worker 内部通信技术(Disruptor)(来自学习资料)

    Storm通信机制 Worker间的通信经常需要通过网络跨节点进行,Storm使用ZeroMQ或Netty(0.9以后默认使用)作为进程间通信的消息框架. Worker进程内部通信:不同worker的 ...

  8. 计算机内部逻辑基础,计算机逻辑基础

    计算机逻辑基础 (9页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 19.90 积分 <计算机组成与工作原理> 第三章 计算机的逻辑基础第三章 ...

  9. 一个老鸟发的公司内部整理的 Android 学习路线图 Markdown 版本

    jixiaohua发了一篇一个老鸟也发了一份他给公司内部小伙伴整理的路线图.另一份 Android 开发学习路线图.可惜不是MarkDown格式的,所以jixiaohua直接上传的截图,在jixiao ...

最新文章

  1. 沈航计算机复试刷人,过来人的血泪教训:复试被刷原因大盘点
  2. HBase常用功能和HBase+MapReduce使用总结
  3. OpenCV 机器视觉入门精选 100 题(附 Python 代码)
  4. db2数据库连接数 linux_介绍一款数据库管理工具DBeaver
  5. python爬虫入门
  6. 国标28181: 视频国标28181协议
  7. SQL语句查询:查询本周过生日的信息
  8. 蚂蚁分类信息系统5.8 短信通道2 互亿无线配置使用说明
  9. 使用 HttpWatch 分析 HTTP 协议
  10. 机械传动机构思维导图
  11. 《红楼梦》香的祭祀文化
  12. php遍历数组查询数据库,php如何遍历数据库查询数组
  13. 2018中国财经文学论坛在杭圆满举行
  14. 文件路径名太长导致IAR编译报错:Fatal Error[Pe1696]: cannot open source file
  15. 照片拼图制作怎么弄?这几个方法或许能帮到你
  16. 谈谈UCloud保障数据安全的七种“武器”
  17. 阿里、京东都在说的赋能到底是什么?
  18. Nginx 设置域名转发到指定端口
  19. css 签名字体,SVG 花样字体文本的自动签名动画
  20. 知名的兴趣社群平台小打卡是如何获得5000万用户的?【黑盒研究内参第11期】...

热门文章

  1. 巧用CSS的alpha滤镜
  2. 【H2645】H.264的宏块和H.265的编码树单元总结
  3. 【Qt】Qt5在ubuntu16.04无法输入中文解决方式
  4. java instanceof 报错_java instanceof方法
  5. mysql实现sass_使用sass绘制三角形
  6. 如何给页面加上loding_如何给片头添加字幕?视频剪辑大神们都这样玩
  7. python进程数上限_python如何控制进程或者线程的个数
  8. Java扫描配置文件的注解_详解Spring框架注解扫描开启之配置细节
  9. linux 生成dll文件,Linux和Windows平台 动态库.so和.dll文件的生成
  10. html文字中横线_谈PPT课件中自定义动画应用之内容控制