runloop事件、UI更新、observer与coranimation
一、触摸事件派发与视图绘制打包
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
__dispatchPreprocessedEventFromEventQueue
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
CA::Transaction::commit()
CA::Context::commit_transaction(CA::Transaction*)
二、UI更新
如果打印App启动之后的主线程RunLoop可以发现另外一个callout为**_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv**的Observer,这个监听专门负责UI变化后的更新,比如修改了frame、调整了UI层级(UIView/CALayer)或者手动设置了setNeedsDisplay/setNeedsLayout之后就会将这些操作提交到全局容器。而这个Observer监听了主线程RunLoop的即将进入休眠和退出状态,一旦进入这两种状态则会遍历所有的UI更新并提交进行实际绘制更新。
通常情况下这种方式是完美的,因为除了系统的更新,还可以利用setNeedsDisplay等方法手动触发下一次RunLoop运行的更新。但是如果当前正在执行大量的逻辑运算可能UI的更新就会比较卡,因此facebook推出了AsyncDisplayKit来解决这个问题。AsyncDisplayKit其实是将UI排版和绘制运算尽可能放到后台,将UI的最终更新操作放到主线程(这一步也必须在主线程完成),同时提供一套类UIView或CALayer的相关属性,尽可能保证开发者的开发习惯。这个过程中AsyncDisplayKit在主线程RunLoop中增加了一个Observer监听即将进入休眠和退出RunLoop两种状态,收到回调时遍历队列中的待处理任务一一执行。
三、runloop6类事件与调度策略
简单的说,RunLoop是事件驱动的一个大循环,如下代码所示
int main(int argc, char * argv[]) {
//程序一直运行状态
while (AppIsRunning) {
//睡眠状态,等待唤醒事件
id whoWakesMe = SleepForWakingU p();
//得到唤醒事件
id event = GetEvent(whoWakesMe);
//开始处理事件
HandleEvent(event);
}
return 0;
}
RunLoop主要处理以下6类事件:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();
- Observer事件,runloop中状态变化时进行通知。(微信卡顿监控就是利用这个事件通知来记录下最近一次main runloop活动时间,在另一个check线程中用定时器检测当前时间距离最后一次活动时间过久来判断在主线程中的处理逻辑耗时和卡主线程)。这里还需要特别注意,CAAnimation是由RunloopObserver触发回调来重绘,接下来会讲到。
- Block事件,非延迟的NSObject PerformSelector立即调用,dispatch_after立即调用,block回调。
- Main_Dispatch_Queue事件:GCD中dispatch到main queue的block会被dispatch到main loop执行。
- Timer事件:延迟的NSObject PerformSelector,延迟的dispatch_after,timer事件。
- Source0事件:处理如UIEvent,CFSocket这类事件。需要手动触发。触摸事件其实是Source1接收系统事件后在回调 __IOHIDEventSystemClientQueueCallback() 内触发的 Source0,Source0 再触发的 _UIApplicationHandleEventQueue()。source0一定是要唤醒runloop及时响应并执行的,如果runloop此时在休眠等待系统的 mach_msg事件,那么就会通过source1来唤醒runloop执行。
- Source1事件:处理系统内核的mach_msg事件。(推测CADisplayLink也是这里触发)。
RunLoop执行顺序的伪代码
SetupThisRunLoopRunTimeoutTimer(); // by GCD timer
//通知即将进入runloop__CFRUNLLOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(KCFRunLoopEntry);
do {
__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks(); //一个循环中会调用两次,确保非延迟的NSObject PerformSelector调用和非延迟的dispatch_after调用在当前runloop执行。还有回调block
__CFRunLoopDoSource0(); //例如UIKit处理的UIEvent事件
CheckIfExistMessagesInMainDispatchQueue(); //GCD dispatch main queue
__CFRunLoopDoObservers(kCFRunLoopBeforeWaiting); //即将进入休眠,会重绘一次界面
var wakeUpPort = SleepAndWaitForWakingUpPorts();
// mach_msg_trap,陷入内核等待匹配的内核mach_msg事件
// Zzz...
// Received mach_msg, wake up
__CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
// Handle msgs
if (wakeUpPort == timerPort) {
__CFRunLoopDoTimers();
} else if (wakeUpPort == mainDispatchQueuePort) {
//GCD当调用dispatch_async(dispatch_get_main_queue(),block)时,libDispatch会向主线程的runloop发送mach_msg消息唤醒runloop,并在这里执行。这里仅限于执行dispatch到主线程的任务,dispatch到其他线程的仍然是libDispatch来处理。
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
} else {
__CFRunLoopDoSource1(); //CADisplayLink是source1的mach_msg触发?
}
__CFRunLoopDoBlocks();
} while (!stop && !timeout);
//通知observers,即将退出runloop
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBERVER_CALLBACK_FUNCTION__(CFRunLoopExit);
结合上面的Runloop事件执行顺序,思考下面代码逻辑中为什么可以标识tableview是否reload完成
dispatch_async(dispatch_get_main_queue(), ^{
_isReloadDone = NO;
[tableView reload]; //会自动设置tableView layoutIfNeeded为YES,意味着将会在runloop结束时重绘table
dispatch_async(dispatch_get_main_queue(),^{
_isReloadDone = YES;
});
});
提示:这里在GCD dispatch main queue中插入了两个任务,一次RunLoop有两个机会执行GCD dispatch main queue中的任务,分别在休眠前和被唤醒后。
iOS 为什么必须在主线程中操作UI
因为UIKit不是线程安全的。试想下面这几种情况:
- 两个线程同时设置同一个背景图片,那么很有可能因为当前图片被释放了两次而导致应用崩溃。
- 两个线程同时设置同一个UIView的背景颜色,那么很有可能渲染显示的是颜色A,而此时在UIView逻辑树上的背景颜色属性为B。
- 两个线程同时操作view的树形结构:在线程A中for循环遍历并操作当前View的所有subView,然后此时线程B中将某个subView直接删除,这就导致了错乱还可能导致应用崩溃。
iOS4之后苹果将大部分绘图的方法和诸如 UIColor 和 UIFont 这样的类改写为了线程安全可用,但是仍然强烈建议讲UI操作保证在主线程中执行。
事件响应
苹果注册了一个 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 事件都是在这个回调中完成的。
CALayer
在iOS当中,所有的视图都从一个叫做UIVIew的基类派生而来,UIView可以处理触摸事件,可以支持基于Core Graphics绘图,可以做仿射变换(例如旋转或者缩放),或者简单的类似于滑动或者渐变的动画。
CALayer类在概念上和UIView类似,同样也是一些被层级关系树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置。它们有一些方法和属性用来做动画和变换。和UIView最大的不同是CALayer不处理用户的交互。CALayer并不清楚具体的响应链。
UIView和CALayer是一个平行的层级关系,每一个UIView都有一个CALayer实例的图层属性,也就是所谓的backing layer,视图的职责就是创建并管理这个图层,以确保当子视图在层级关系中添加或者被移除的时候,他们关联的图层也同样对应在层级关系树当中有相同的操作。实际上这些背后关联的Layer图层才是真正用来在屏幕上显示和做动画,UIView仅仅是对它的一个封装,提供了一些iOS类似于处理触摸的具体功能,以及Core Animation底层方法的高级接口。
UIView 的 Layer 在系统内部,被维护着三份同样的树形数据结构,分别是:
- 图层树(这里是代码可以操纵的,设置属性的最终值会立刻在这里更新);
- 呈现树(是一个中间层,系统就在这一层上更改属性,进行各种渲染操作。比如一个动画是更改alpha值从0到1,那么在逻辑树上此属性会被立刻更新为最终属性1,而在动画树上会根据设置的动画时间从0逐步变化到1);
- 渲染树(其属性值就是当前正被显示在屏幕上的属性值);
http://www.cnblogs.com/Twisted-Fate/p/4892192.html
四、几个主要的observer
NSRunLoop 三个主要的observer:
1、autoreleasepool;
2、手势识别;
3、动画;
observers = (
"<CFRunLoopObserver 0x600000e145a0 [0x10389db68]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10648a1b1), context = <CFArray 0x600003158a20 [0x10389db68]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fa317002058>\n)}}",
"<CFRunLoopObserver 0x600000e10140 [0x10389db68]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x10605c473), context = <CFRunLoopObserver context 0x6000014102a0>}",
"<CFRunLoopObserver 0x600000e14460 [0x10389db68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x1064b9dfc), context = <CFRunLoopObserver context 0x7fa315502e10>}",
"<CFRunLoopObserver 0x600000e14b40 [0x10389db68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x107eb86ae), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x600000e143c0 [0x10389db68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x1064b9e75), context = <CFRunLoopObserver context 0x7fa315502e10>}",
"<CFRunLoopObserver 0x600000e146e0 [0x10389db68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10648a1b1), context = <CFArray 0x600003158a20 [0x10389db68]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fa317002058>\n)}}"
),
runloop事件、UI更新、observer与coranimation的更多相关文章
- C#.NET使用Task,await,async,异步执行控件耗时事件(event),不阻塞UI线程和不跨线程执行UI更新,以及其他方式比较
使用Task,await,async,异步执行事件(event),不阻塞UI线程和不跨线程执行UI更新 使用Task,await,async 的异步模式 去执行事件(event) 解决不阻塞UI线程和 ...
- 【Cocos2d-Js基础教学(7)界面UI更新方法(会用到第三方类库)】
我们游戏中会遇到很多UI更新的时候,大部分时候我们会remove该节点,再重新绘制的方法来进行UI更新. 但是这种更新效率并不高,这里我推荐大家一个第三方的库,来通过注册更新的方式来对UI进行更新管理 ...
- RxJava2-后台执行耗时操作,实时通知 UI 更新(一)
一.前言 接触RxJava2已经很久了,也看了网上的很多文章,发现基本都是在对RxJava的基本思想介绍之后,再去对各个操作符进行分析,但是看了之后感觉过了不久就忘了. 偶然的机会看到了开源项目 Rx ...
- [UE4]RetainerBox,控制UI更新频率,把渲染后的UI当成Texture
RetainerBox是一个容器,只会影响其容器内的UI,RetainerBox的作用: 一.控制UI更新频率(可能是为有优化性能) 1.在UserWidget中添加Retainer Box容器,并在 ...
- 【Flutter】功能型组件之异步UI更新
前言 很多时候会依赖一些异步数据来动态更新UI,比如在打开一个页面时我们需要先从互联网上获取数据,在获取数据的过程中我们显示一个加载框,等获取到数据时我们再渲染页面:又比如想展示Stream(比如文件 ...
- android开发之在activity中控制另一个activity的UI更新
转自:http://blog.csdn.net/jason0539/article/details/18075293 第一种方法: 遇到一个问题,需要在一个activity中控制另一个acitivit ...
- WPF多线程UI更新——两种方法
WPF多线程UI更新——两种方法 前言 在WPF中,在使用多线程在后台进行计算限制的异步操作的时候,如果在后台线程中对UI进行了修改,则会出现一个错误:(调用线程无法访问此对象,因为另一个线程拥有该对 ...
- oc 多线程UI更新
1.在子线程中是不能进行UI 更新的,而可以更新的结果只是一个幻像:因为子线程代码执行完毕了,又自动进入到了主线程,执行了子线程中的UI更新的函数栈,这中间的时间非常的短,就让大家误以为分线程可以更新 ...
- highcharts图表组件入门教程:如何监听柱状图柱子点击事件动态更新当前数据点数值和所对应X轴刻度
highcharts图表组件入门教程:如何监听柱状图柱子点击事件动态更新当前数据点数值和所对应X轴刻度 作者:highcharts | 时间:2014-6-11 14:07:05 | [小 大] | ...
随机推荐
- 【转】用 async/await 来处理异步
原文地址:https://www.cnblogs.com/SamWeb/p/8417940.html 昨天看了一篇vue的教程,作者用async/ await来发送异步请求,从服务端获取数据,代码很简 ...
- 前端与算法 leetcode 125. 验证回文串
目录 # 前端与算法 leetcode 125. 验证回文串 题目描述 概要 提示 解析 解法一:api侠 解法二:双指针 算法 传入测试用例的运行结果 执行结果 GitHub仓库 查看更多 # 前端 ...
- Spring Cloud config之三:config-server因为server端和client端的健康检查导致服务超时阻塞问题
springcloud线上一个问题,当config-server连不上git时,微服务集群慢慢的都挂掉. 在入口层增加了日志跟踪问题: org.springframework.cloud.config ...
- django开发_七牛云图片管理
七牛云注册 https://www.qiniu.com/ 实名认证成功之后,赠送10G存储空间 复制粘贴AK和SK 创建存储空间,填写空间名称,选择存储区域.访问控制选择位公开空间 获取测试域名 七牛 ...
- 【题解】Luogu P5470 [NOI2019]序列
原题传送门 同步赛上我一开始想了个看似正确却漏洞百出的贪心:按\(a_i+b_i\)的和从大向小贪心 随便想想发现是假的,然后就写了个28pts的暴力dp 杜神后半程说这题就是个贪心,但我没时间写了 ...
- 『2019 SummerCamp 总结』
做题 对于习题方面,我们感觉一个暑假还是留下了不少的题要写,大部分应该是讲师讲课的例题,还有少部分考试题.考试题没有订正完是因为还有算法不会,或是因为题太毒瘤了不会.同时,也发现自己还是有很多应该学的 ...
- Linux中常用命令pipe
大多数linux命令处理数据后都会输出到标准输出,但是如果数据要经过系列列的步骤处理后,才是需要的数据个数,这种需求就需要管道来帮助完成. 管道命令使用"|"作为界定符,将界定符前 ...
- PTA 甲级 1139
https://pintia.cn/problem-sets/994805342720868352/problems/994805344776077312 其实这道题目不难,但是有很多坑点! 首先数据 ...
- 面试题 js重写原生函数(以push为例)
先说明一下为什么要写这个,因为最近在面试,面试的时候面试官问了这个问题,当时是真的没有答上来,回来之后自己考虑了一下,现在给大家分享 要求如下: 重写js push函数,使其能够在push的同时打印出 ...
- 可落地的DDD(3)-如何利用DDD进行微服务的划分
摘要 前面两篇介绍了DDD的目标管理.DDD的工程结构调整.这篇讨论微服务的划分.微服务是目前后端比较流行的架构体系了,那么如何做好一个微服务的划分?一个微服务的粒度应该是多大呢?这篇主要介绍如何结合 ...