CFRunLoopRef 的内部逻辑(向 ibireme学习)
据苹果在文档里的说明,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:
kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
- 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学习)相关推荐
- ASP.NET Core分布式项目实战(Consent 确认逻辑实现)--学习笔记
任务22:Consent 确认逻辑实现 接下来,我们会在上一节的基础上添加两个按钮,同意和不同意,点击之后会把请求 post 到 ConsentController 处理,如果同意会通过 return ...
- InCallContrller内部逻辑
前言 刚刚开始看InCallContrller内部逻辑是会有点复杂的所以我今天来做一个笔记. 作用 : InCallContrller的作用就是为了调用InCallUi页面,InCallContrll ...
- 关于汽车仪表中车速表的内部逻辑
车辆上车速表显示车速和实际车速 我们从车速表上看到的速度,其实是仪表想让驾驶员看到的"假"速度(GB 15082中称为指示车速),它不是车辆当前的"真"速度(G ...
- zynq7000 学习(二十四)VGA 接口原理分析和控制逻辑的实现学习
VGA 接口原理分析和控制逻辑的实现学习 学习内容 本课程首先 进行 VGA 接口 的原理介绍,然后 编写逻辑 控制代码,最终进行 仿真和实现. 本课程 主要包括原理介绍和 设计实现 两个部分. 原理 ...
- 阅读 | 《逻辑工作法》学习笔记
<逻辑工作法>学习笔记 PART1 避免被上司批评的思维方式 PART2 科学安排时间,避免无意义的加班 PART3 让身边人刮目相看的思维方式 PART4 流畅顺利地编写资料的写作技巧 ...
- 【教程】手把手教你如何利用工具(IE9的F12)去分析模拟登陆网站(百度首页)的内部逻辑过程
声明:本文章转载自crifan的技术人生 [教程]手把手教你如何利用工具(IE9的F12)去分析模拟登陆网站(百度首页)的内部逻辑过程 重要提示: 1.此贴,以后不再更新: 2.想要看更新的内容,请移 ...
- Storm通信机制,Worker进程间通信,Worker进程间通信分析,Worker进程间技术(Netty、ZeroMQ),Worker 内部通信技术(Disruptor)(来自学习资料)
Storm通信机制 Worker间的通信经常需要通过网络跨节点进行,Storm使用ZeroMQ或Netty(0.9以后默认使用)作为进程间通信的消息框架. Worker进程内部通信:不同worker的 ...
- 计算机内部逻辑基础,计算机逻辑基础
计算机逻辑基础 (9页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 19.90 积分 <计算机组成与工作原理> 第三章 计算机的逻辑基础第三章 ...
- 一个老鸟发的公司内部整理的 Android 学习路线图 Markdown 版本
jixiaohua发了一篇一个老鸟也发了一份他给公司内部小伙伴整理的路线图.另一份 Android 开发学习路线图.可惜不是MarkDown格式的,所以jixiaohua直接上传的截图,在jixiao ...
最新文章
- 沈航计算机复试刷人,过来人的血泪教训:复试被刷原因大盘点
- HBase常用功能和HBase+MapReduce使用总结
- OpenCV 机器视觉入门精选 100 题(附 Python 代码)
- db2数据库连接数 linux_介绍一款数据库管理工具DBeaver
- python爬虫入门
- 国标28181: 视频国标28181协议
- SQL语句查询:查询本周过生日的信息
- 蚂蚁分类信息系统5.8 短信通道2 互亿无线配置使用说明
- 使用 HttpWatch 分析 HTTP 协议
- 机械传动机构思维导图
- 《红楼梦》香的祭祀文化
- php遍历数组查询数据库,php如何遍历数据库查询数组
- 2018中国财经文学论坛在杭圆满举行
- 文件路径名太长导致IAR编译报错:Fatal Error[Pe1696]: cannot open source file
- 照片拼图制作怎么弄?这几个方法或许能帮到你
- 谈谈UCloud保障数据安全的七种“武器”
- 阿里、京东都在说的赋能到底是什么?
- Nginx 设置域名转发到指定端口
- css 签名字体,SVG 花样字体文本的自动签名动画
- 知名的兴趣社群平台小打卡是如何获得5000万用户的?【黑盒研究内参第11期】...
热门文章
- 巧用CSS的alpha滤镜
- 【H2645】H.264的宏块和H.265的编码树单元总结
- 【Qt】Qt5在ubuntu16.04无法输入中文解决方式
- java instanceof 报错_java instanceof方法
- mysql实现sass_使用sass绘制三角形
- 如何给页面加上loding_如何给片头添加字幕?视频剪辑大神们都这样玩
- python进程数上限_python如何控制进程或者线程的个数
- Java扫描配置文件的注解_详解Spring框架注解扫描开启之配置细节
- linux 生成dll文件,Linux和Windows平台 动态库.so和.dll文件的生成
- html文字中横线_谈PPT课件中自定义动画应用之内容控制