Hi Coder,我是 CoderStar!
上周介绍了一下iOS 页面渲染-UIView & CALayer,本周我们来聊一聊 iOS 页面渲染中的高频面试题--离屏渲染。
其实给大家先分享关于 iOS 页面渲染的相关知识有一个原因是为后续 iOS 优化系列中的 UI 渲染优化篇做铺垫,方便大家在后面阅读时能够清楚优化手段背后的原理以及有一个更深的理解。
在开始本文之前,我先谈谈上周我们《iOS 摸鱼周报》发起人 @zhangferry 访谈我的时候我对访谈问题给的一些答复吧。
自己:CoderStar,坐标北京,目前主要工作与 iOS 相关,对大前端、后端都有一定涉猎,喜欢分享干货博文。 公众号:CoderStar,分享大前端相关的技术知识,只聊技术干货,目前分享的内容主要是 iOS 相关的,后续还会分享一些 Flutter、Vue 前端等相关技术知识。目前公众号文章内容均是自己原创,很欢迎大家投稿一些好文章,大家一块进步。
最开始写公众号的原因其实比较简单:
写公众号的好处:
最近在做优化方面的事情,未来几篇文章可能会偏向优化系列或者底层相关。
我目前更新的频率是一周一篇文章,一般工作日晚上会去看一些本期文章涉及的资料以及做一些代码实践,然后积累一些笔记,在周末时候将笔记进行整理聚合,形成文章,其实这个过程中还是比较累的,毕竟有的时候工作会忙,但是这个事情一定要坚持,给自己一个目标,不能随随便便就断更,毕竟有第一次断更就有第二次。 学习方法:说一点吧,我自己对于技术的态度是实践型 + 更优解,当看到一些好的文章的时候,会自己将文章里面的原理或者实现自己动手实践一下,考虑这个方法有什么缺点,并围绕这个技术点去思考有没有更好的解决方案,不断地去寻找更优解。
关于访谈部分就说这么多,希望我的一些心得能给大家提供一些借鉴。好了,我们接下来言归正传,技术干货开始了。
其实本周是准备分享我对某几个设计模式的心得体会给大家的,但后来考虑到内容的连贯性以及不确定大家对设计模式的感兴趣程度,就放弃了这个想法,如果大家对设计模式比较感兴趣,可以通过点赞来表达一下。
先简单说下 iOS 页面渲染的正常流程。
如果要在显示屏上显示内容,我们至少需要一块与屏幕像素数据量一样大的 Framebuffer
,作为像素数据存储区域,GPU 不停地将渲染完成后的内容放入 Framebuffer
帧缓冲器中,而显示屏幕不断地从 Framebuffer
中获取内容,显示实时的内容。
FrameBuffer
如果有时因为面临一些限制,无法把渲染结果直接写入 Framebuffer
,我们就需要先额外创建离屏渲染缓冲区 Offscreen Buffer
,将提前渲染好的内容放入其中,等到合适的时机再将 Offscreen Buffer
中的内容进一步叠加、渲染,完成后将结果切换到 Framebuffer
中,那么这个过程便被称之为离屏渲染。
OffscreenBuffer
对于上周文章所提到的利用
Core Graphics
的 API 进行页面绘制的方式有时候也会被称为离屏渲染
(因为像素数据是暂时存入了 CGContext,而不是直接到了 frame buffer),但是按照苹果工程师说法[1],这种绘制方式发生在 CPU 中,并非是真正意义上的离屏渲染,其实通过 CPU 渲染就是俗称的'软件渲染',而真正的离屏渲染发生在 GPU,我们这里研究的更多是 GPU 的离屏渲染。
通常情况下来说, 离屏渲染非常消耗性能, 主要体现在两个方面:
Offscreen Buffer
的总大小也有限,不能超过屏幕总像素的 2.5 倍;一旦需要离屏渲染的内容过多,很容易造成掉帧的问题。所以大部分情况下,我们都应该尽量避免离屏渲染。
既然离屏渲染对性能有损伤,那为什么还要使用离屏渲染呢?主要有两种原因:
Offscreen Buffer
来保存渲染的中间状态,所以不得不使用离屏渲染;Offscreen Buffer
中,达到复用的目的。对于第一种情况,也就是不得不使用离屏渲染的情况,一般都是系统自动触发的,比如mask
、UIBlurEffectView
等。下文会介绍具体哪些常见的场景会发生离屏渲染。
对于第二种情况,我们可以利用开启CALayer
的shouldRasterize
属性去触发离屏渲染。开启之后,Render Server
会强制将 CALayer 的渲染位图结果
bitmap` 保存下来,这样下次再需要渲染时就可以直接复用,从而提高效率。
保存的 bitmap
包含 layer
的 subLayer
、圆角、阴影、组透明度 group opacity
等,所以如果 layer
的构成包含上述几种元素,结构复杂且需要反复利用,那么就可以考虑打开光栅化。其主旨在于降低性能损失,但总是至少会触发一次离屏渲染。
圆角、阴影、组透明度等会由系统自动触发离屏渲染,那么打开光栅化就可以节约第二次及以后的渲染时间。而多层 subLayer 的情况由于不会自动触发离屏渲染,所以相比之下会多花费第一次离屏渲染的时间,但是可以节约后续的重复渲染的开销。
不过使用光栅化的时候需要注意以下几点:
layer
本来并不复杂,也没有圆角阴影等等,则没有必要打开光栅化;layer
不能被复用,则没有必要打开光栅化;layer
的内容(包括子 layer)必须是静态的,因为一旦发生变化(如 resize,动画),之前辛苦处理得到的缓存就失效了。所以如果layer
不是静态,需要被频繁修改,比如处于动画之中,那么开启离屏渲染反而影响效率;100ms
内如果没有被使用,那么就会被丢弃,无法进行复用;2.5
倍屏幕像素大小的话也会失效,无法复用。其实除了解决多次离屏渲染的开销,
shouldRasterize
在另一个场景中也可以使用:如果 layer 的子结构非常复杂,渲染一次所需时间较长,同样可以打开这个开关,把 layer 绘制到一块缓存,然后在接下来复用这个结果,这样就不需要每次都重新绘制整个 layer 树了。
图层的叠加绘制大概遵循画家算法,在这种算法下会按层绘制,首先绘制距离较远的场景,然后用绘制距离较近的场景覆盖较远的部分。
画家算法
在普通的 layer 绘制中,上层的 sublayer 会覆盖下层的 sublayer,下层 sublayer 绘制完之后就可以抛弃了,从而节约空间提高效率。所有 sublayer 依次绘制完毕之后,整个绘制过程完成,就可以进行后续的呈现了。
而有些场景并没有那么简单。GPU 虽然可以一层一层往画布上进行输出,但是无法在某一层渲染完成之后,再回过头来擦除 / 改变其中的某个部分——因为在这一层之前的若干层 layer 像素数据,已经在渲染中被永久覆盖了。这就意味着,对于每一层 layer,要么能找到一种通过单次遍历就能完成渲染的算法,要么就不得不另开一块内存,借助这个临时中转区域来完成一些更复杂的、多次的修改 / 剪裁操作。
我们先打开模拟器 Debug 下的离屏渲染颜色标记,如左图所示,当出现离屏渲染时,相应控件会出现如右图所示的黄色。
离屏渲染标记
通过我们上面离屏渲染发生的原因,其实我们可以很简单的归纳出离屏渲染出现的场景。
只要裁剪的内容需要画家算法未完成之前的内容参与就会触发离屏渲染。
总结一下,下面几种情况会触发离屏渲染:
还有一个会触发离屏渲染的场景是我们非常常见的 -- 圆角,这个需要着重说明一下。
我们经常看到,圆角会触发离屏渲染。但其实这个说法是不准确的,因为圆角触发离屏渲染也是有条件的!
我们先看一下苹果官方文档对于cornerRadius
的描述:
Discussion Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners. The default value of this property is 0.0.
设置 cornerRadius
大于 0 时,只为 layer 的 backgroundColor
和 border
设置圆角;而不会对 layer
的 contents
设置圆角,除非同时设置了 layer.masksToBounds
为 true
(对应 UIView
的 clipsToBounds
属性)。
但是当layer.masksToBounds
或者clipsToBounds
设置为 true,也不一定会触发离屏渲染。
当我们设置了圆角 + 裁剪之后,还需要我们为 contents 设置了内容才会触发离屏渲染,其中为 contents 设置了内容的方式不一定是直接为 layer 的 contents 属性赋值,还包括添加有图像信息的子视图等方式。
关于圆角,iOS 9 及之后的系统版本,苹果进行了一些优化。我们只设置 layer
的 contents
或者 UIImageView
的 image
,并加上圆角 + 裁剪,是不会产生离屏渲染的。但如果加上了背景色、边框或其他有图像内容的图层,还是会产生离屏渲染。
总结一下,iOS 9 之后圆角造成离屏渲染的条件包括:
有些结论一定要自己去试一下,就比如说我上面的结论也不一定是对的,因为可能还有我没注意到的 case。
既然圆角 + 裁剪在一定情况下会产生离屏渲染,那么有什么方式可以帮助我们在不产生离屏渲染绘制圆角效果呢?如下:
新的一周要更加努力呀!
Let's be CoderStar!
参考链接
[1]苹果工程师说法: https://lobste.rs/s/ckm4uw/performance_minded_take_on_ios_design#c_itdkfh
[2]iOS Rendering 渲染全解析(长文干货): https://juejin.cn/post/6844904162765832206#heading-17
[3]关于 iOS 离屏渲染的深入研究: https://zhuanlan.zhihu.com/p/72653360
[4]iOS 界面渲染与优化(四) - 离屏渲染与优化总结: https://juejin.cn/post/6982909100275269662
[5]iOS 圆角的离屏渲染,你真的弄明白了吗: https://juejin.cn/post/6846687603316490254#heading-2
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有