POP 库基于 CADisplayLink
注册 VSync 信号,如何注册的呢?VSync 信号到底是个啥呢?首先,我们打印一下 CADisplayLink 的触发堆栈:
我们发现这是一个从 Port 转发过来处理的 source1,我们打印一下 Runloop,发现比没有 CADisplayLink
的 Runloop 多了一个 source1:
2 : <CFRunLoopSource 0x282dc8000 [0x1cadcf728]>{
signalled = No, valid = Yes, order = -1,
context = <CFMachPort 0x282fd8160 [0x1cadcf728]>{
valid = Yes, port = 440b, source = 0x282dc8000,
callout = _ZL22display_timer_callbackP12__CFMachPortPvlS1_ (0x187592b2c),
context = <CFMachPort context 0x2823d0000>
}
}
有这样一个 source1 被加入了 Runloop。CADisplaylink 也是基于这个 port完成了 VSync 信号的注册工作。产生 VSync 信号的进程,每 16.7ms 进行一次到这个 port 的 mach msg 发送工作,从而不断的激活本 App 的 Runloop ,触发一个 item,完成本 App 对 VSync 的感知。这里说点题外的,看到item,很自然的联想到这里是不是一个链表之类的数据结构。实际上,我们可以多次添加不同的 CADisplayLink instance 到 Runloop 中,就像这样:
CADispalyLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawSomething)];
[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
CADispalyLink *link2 = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawSomething2)];
[link2 addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
但是如果这样写:
CADispalyLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawSomething)];
[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
CADispalyLink *link2 = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawSomething)];
[link2 addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
绿色高亮部分的差异会造成什么呢?drawSomeThing
在一个 VSync message 里被触发了两次。原因是:[CADisplayLink displayLinkWithTarget:self selector:@selector(drawSomeThing)]
这个方法内部的 [CADisplayLink displayLinkWithDisplay:target:selector:]
调用新生成了一个 DisplayLinkItem
(下图汇编代码中的 v8)。
调用 [CADisplayLink addToRunLoop:forMode:]
时,内部:
这里的逻辑只检测了:
下面我们总结一下基于CoreAnimation的Animation和基于POP的Animation的区别,可以用以下流程图解答: 总结一下:
但是显而易见,有以下不足:
总结可得到以下两个问题的答案:
如何进入 waiting?
调用 mach_msg
,设置好 timeout
参数和 wait_port
就可以进入内核态等待 mach_msg
的来临
如何 wakeup?
说了这么一圈,我们了解了Runloop本质是在处理mach msg。那么我们什么时候做渲染呢?viewdidload
、layoutSubviews
等等中代码,到底是在 Runloop 哪里被触发的呢?
由于 Runloop 比较多的 mode ,每个 mode 都是在组合常见的几个 item ,关于 mode 和 item 不清楚的同学可以自行查阅相关资料,这里打印出一个 Runloop 的 debugDescription 来说明:
<CFRunLoop 0x6000001d8100 [0x1c04a6510]>{wakeup port = 0x2303, stopped = false, ignoreWakeUps = false, current mode = kCFRunLoopDefaultMode,//首先,目前Runloop中存在两个mode,一个是UITracking,也就是滑动用的,一个是defaultcommon modes = <CFBasicHash 0x600003390ed0 [0x1c04a6510]>{type = mutable set, count = 2,entries => 0 : <CFString 0x1c0b2e5f0 [0x1c04a6510]>{contents = "UITrackingRunLoopMode"} 2 : <CFString 0x1c067c148 [0x1c04a6510]>{contents = "kCFRunLoopDefaultMode"}}
第一个mode——UITrackingRunLoopMode:
0 : <CFRunLoopMode 0x6000006d4410 [0x1c04a6510]>{ name = UITrackingRunLoopMode, port set = 0x4803, queue = 0x6000013d4e00, source = 0x6000013d4f00 (not fired), timer port = 0x4703, sources0 : { 0 : <CFRunLoopSource >{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x18aebe530)}} 1 : <CFRunLoopSource >{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000019c4a20, callout = FBSSerialQueueRunLoopSourceHandler (0x185729edc)}} 2 : <CFRunLoopSource >{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x6000006c0000, callout = __eventQueueSourceCallback (0x1844477dc)}} 5 : <CFRunLoopSource >{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600003381140, callout = __eventFetcherSourceCallback (0x184447850)}} }, sources1 : { 0 : <CFRunLoopSource >{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3603, callout = PurpleEventCallback (0x18aebe538)}} }, observers = ( "<CFRunLoopObserver repeats = Yes, order = -2147483647, callout = _runLoopObserverCallout (0x183ee5508), context = (<_UIWeakReference: 0x600003fd4250>)}", "<CFRunLoopObserver repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x183f32638), context = <CFRunLoopObserver context 0x6000016c0000>}", "<CFRunLoopObserver repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x1843dbf00), context = <CFRunLoopObserver context 0x125705a10>}", "<CFRunLoopObserver repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x187561d54), context = <CFRunLoopObserver context 0x0>}", "<CFRunLoopObserver repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x1843dbf74), context = <CFRunLoopObserver context 0x125705a10>}", "<CFRunLoopObserver repeats = Yes, order = 2147483647, callout = _runLoopObserverCallout (0x183ee5508), context = (\n \"<_UIWeakReference: 0x600003fd4250>\"\n)}" ), timers = (null),},
比较有价值的,就是这个 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv
函数了他是由 CoreAnimation 注册到 Runloop 的一个 observer 的回调。在这个 mode 下只有一个 source1,说明现在(仅代表测试程序环境)只能被来一个端口 msg 中断,也就是 PurpleEventCallback
对应的端口。
第二个 mode -GSEventReceiveRunLoopMode
:没有暴露的 mode,也是跟事件下发相关,不做探究
1 : <CFRunLoopMode 0x6000006d44e0 [0x1c04a6510]>{ name = GSEventReceiveRunLoopMode, port set = 0x4603, queue = 0x6000013d4f80, source = 0x6000013d5080 (not fired), timer port = 0x4403, sources0 = <CFBasicHash 0x600003385380 [0x1c04a6510]>{type = mutable set, count = 1, entries => 0 : <CFRunLoopSource 0x6000008d40c0 [0x1c04a6510]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x18aebe530)}} }, sources1 = <CFBasicHash 0x6000033853b0 [0x1c04a6510]>{type = mutable set, count = 1, entries => 0 : <CFRunLoopSource 0x6000008d4480 [0x1c04a6510]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3603, callout = PurpleEventCallback (0x18aebe538)}} }, observers = (null), timers = (null), currently 628607093 (779024795576) / soft deadline in: 7.68614304e+11 sec (@ -1) / hard deadline in: 7.68614304e+11 sec (@ -1)},
第三个 mode - kCFRunLoopDefaultMode
。
2 : <CFRunLoopMode 0x6000006dc000 [0x1c04a6510]>{ name = kCFRunLoopDefaultMode, port set = 0x5403, queue = 0x6000013dc700, source = 0x6000013dc800 (not fired), timer port = 0x2c03, sources0 = <CFBasicHash 0x600003385290 [0x1c04a6510]>{type = mutable set, count = 4, entries => 0 : <CFRunLoopSource 0x6000008d40c0 [0x1c04a6510]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x18aebe530)}} 1 : <CFRunLoopSource 0x6000008dc000 [0x1c04a6510]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000019c4a20, callout = FBSSerialQueueRunLoopSourceHandler (0x185729edc)}} 2 : <CFRunLoopSource 0x6000008d8180 [0x1c04a6510]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x6000006c0000, callout = __eventQueueSourceCallback (0x1844477dc)}} 5 : <CFRunLoopSource 0x6000008d8240 [0x1c04a6510]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600003381140, callout = __eventFetcherSourceCallback (0x184447850)}} }, sources1 = <CFBasicHash 0x6000033852f0 [0x1c04a6510]>{type = mutable set, count = 1, entries => 0 : <CFRunLoopSource 0x6000008d43c0 [0x1c04a6510]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3603, callout = PurpleEventCallback (0x18aebe538)}} }, observers = ( "<CFRunLoopObserver 0x600000cdc460 [0x1c04a6510]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _runLoopObserverCallout (0x183ee5508), context = (\n \"<_UIWeakReference: 0x600003fd4250>\"\n)}", "<CFRunLoopObserver 0x600000cc0000 [0x1c04a6510]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x183f32638), context = <CFRunLoopObserver context 0x6000016c0000>}", "<CFRunLoopObserver 0x600000cdc780 [0x1c04a6510]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x1843dbf00), context = <CFRunLoopObserver context 0x125705a10>}", "<CFRunLoopObserver 0x600000cd08c0 [0x1c04a6510]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x187561d54), context = <CFRunLoopObserver context 0x0>}", "<CFRunLoopObserver 0x600000cdc500 [0x1c04a6510]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x1843dbf74), context = <CFRunLoopObserver context 0x125705a10>}", "<CFRunLoopObserver 0x600000cdc820 [0x1c04a6510]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _runLoopObserverCallout (0x183ee5508), context = (\n \"<_UIWeakReference: 0x600003fd4250>\"\n)}" ), timers = <CFArray 0x6000019c82a0 [0x1c04a6510]>{type = mutable-small, count = 1, values = ( 0 : <CFRunLoopTimer 0x6000008d0180 [0x1c04a6510]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 628607095 (1.47744203 @ 779060301174), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x1808078f4 / 0x183895594) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore), context = <CFRunLoopTimer context 0x600002898080>} )}, currently 628607093 (779024795962) / soft deadline in: 1.47938383 sec (@ 779060301174) / hard deadline in: 1.47938379 sec (@ 779060301174)},
综上,一个普通运行起来的程序,Runloop 中大概有以下几个 source/observer:
AutoReleasePool 是在 Runloop 启动时就生成了一个基本的 pool 来使用,而 transaction 则是有需要才建立的,这一步是怎么处理的呢?前面我们看到, CATransaction 注册了 before waiting 的事件,在 before waiting 的时候,调用CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*)
,这个 callback 里主要调用了CA::Transaction::commit()
。
接下来我们研究一下 CATransaction 机制,并讲解CA::Transaction::commit()
做了什么。
CATransaction 是整个 CoreAnimation 的工作流程核心,它不是对 OpenGLES 的封装的,也没有做动画插值。它是一个载体,大部分业务需求的WorkFlow都是基于CATransaction完成的。基础的用法,begin,commit,timingfunction,complete handler,隐式事务,Runloop 中的大 Transaction 我们就不谈了,不清楚的同学可以自行查阅相关已有资料。CA::Transaction::Commit()
的被调用逻辑:CA::Transaction::Commit
主要由
[CATransaction Flush]
时调用CA::Transaction::Commit
内部主要逻辑如下:
可以看到,我们主要做了这几件事情:
对 layer tree 调用 layoutIfNeeded
在这个过程中,实现从 CoreAnimation 到 UIKit 的上升,即最终调用到我们熟悉的 layoutSubviews
等方法
layoutIfNeeded
理论上只对 setNeedsLayout
过的 UIView(的 Layer)进行 layout 操作,frame、layout constraint等行为,均会 setNeedsLayout
,手动设置也会生效。
因此我们得出结论,通常情况下,大量的 layout`工作会在一次 Runloop 结束的时候全部开始,如果 layout engine 出现很多 broken,或者本身计算量过大(跨层较多,冗余约束较多等原因),则很有可能超时,导致一轮 Runloop 超过 16.7ms,
通过mach与其他线程 (render server) 通信
commitIfNeeded
处理 Animation
下面看 commit_If_Needed
中的具体逻辑:
调用 [CALayer _copyRenderLayer:layerFlags:commitFlags]
基于这个layer的各种属性,创建CA::Render::Object *
有layer级别的cache
根据情况使用ioSurface,data等多种数据源来生成bitmap(image)
调用CA::Render::encode_set_object()
,把前面创建的render obj set进去
调用 [CALayer _didCommitLayer:]
综上,当 App 的 Runloop 不管是被什么事件唤醒以后,总能在进入内核态之前完成当前所有UI 的更新任务,并通过 mach_msg 完成 commit 到 render server 的工作。最常见由 iOS 开发者控制的唤醒 Runloop 的行为有:
基于GCD的
在主线程上 dispatch_after() 或 dispatch_async
网络进程回调,dispatch_async到主线程
基于GS Event的
用户操作屏幕
用户操作物理按键
至此我们已经解答了以下问题:
layoutSubviews
是什么时候触发的有任何你想了解的问题,欢迎在文章下方评论。
计算机动画扩展:计算机动画发展的很快,不再局限于某些物体的位置变换和伸缩变换,出现了很多算法实现各种效果,总结来看,计算机动画分为以下几种:
2D动画:
图像变形(Image Morphing)
形状混合(Shape blending)
3D动画:
关键帧动画
变形物体动画(自由体变形技术,FFD)
那么 CoreAnimation 在动画方面关注的显然是 2D 动画,我们也来讲解一下关于图像变形和形状混合。图像变形图像变形有两种基本的方法:
基于单张图像进行形变
如:宽高拉伸
基于多张图像进行插值
此方法可以引入用户交互,指定某些特征进行动画,更加可控
注意噢,这里的特征可不是 iOS 动画中的 property ,是指对某个图像人为框定几个特征点,进行拉伸形变。
二维图形动画,都可以简化为为多边形处理。二维的形状混合,即在两个关键帧的多边形之间插入新的多边形。插入新的多边形,需要解决的问题就是前后两个关键帧之间,顶点的对应关系和顶点之间插值路径的问题。这两个问题在 siggraph 1992 和 1993 的论文上得到了解。
参考资料
[1]深入理解 RunLoop: https://blog.ibireme.com/2015/05/18/Runloop/
[2]Apple CFRunloop 源码: https://opensource.apple.com/source/CF/CF-635/CFRunLoop.c.auto.html
[3]界面渲染的整体流程: http://blog.handy.wang/blog/2015/10/03/uiviewyu-calayerxie-zuo-xuan-ran-jie-mian-de-guo-cheng/
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/bjs3L5JLPexhqyX6Cxm5_A
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。