原标题为:《Advanced Graphics and Animation For iOS Apps》。下载的文件名有点不同。本笔记内容为视频学习和实践探索。另外 objc 的这篇文章《绘制像素到屏幕上》和本视频的内容有很大程度的交集,推荐一下。
视频内容
- Core Animation pipeline
- Rendering concepts
- UIBlurEffect
- UIVibrancyEffect
- Profiling tools
- Case studies
Core Animation Pipeline
这个章节的时长为06:35,但信息量非常大。每个小节拉出来都能另写一章。
让屏幕页面流畅应该保证页面刷新率为 60 帧/秒,1 帧的时间大概就是 16.67 ms了。Core Animation 这个框架的名字很具有误导性,让大家以为这个框架只是用来实现动画的,实际上 Core Animation 框架做了很多基础工作:组合屏幕上的内容,追踪视图结构和内容的变化。流程图中 Commit Transaction 前面的红框代表触发视图内容变化的事件,比如点击按钮,之后Core Animation 框架会捕获到屏幕内容的变化并提交给 Render Server(渲染服务器),Render Server 里另外一个版本的 Core Animation 框架负责解码并绘制内容。
值得注意的是:在 Prepare 这个阶段做的事是图像解码以及图像转换。无论是网上下载的图像还是从磁盘读取图像文件,得到的图像一般是不能直接用于显示的,需要解码为位图(bitmap)。如果你的视图中使用了 JPEGs 或是 PNGs的图像,将在这个阶段进行解码;如果你使用了 GPU 不支持的图像格式(就是 JPEG 和 PNG 之外的格式),那么图像就需要转换格式。Path 团队的图像缓存开源库FastImageCache就利用这个特性来加速图像的显示。
Rendering concepts
这一节介绍了一些基本的渲染知识:屏幕被分割成 NxN 像素的小块来渲染,每个小块的大小与 SoC(System on Chip) 的cache相关。具体的操作过程如下:对于一个 app icon,被当做一个 CALayer 来渲染,而 CALayer 在 Core Animation 中被划分为两个三角形,每个三角形可以被继续分割成多个三角形,对每一个三角形单独渲染。
后面的,我看完之后基本就忘了,所以想了解到底怎么渲染的可以看看这一段。大致过程是渲染然后合成,在开头的文章中有对这块的叙述。
UIVisualEffectView
在 iOS 8 中苹果放出了新的 UIView
子类 UIVisualEffectView
。苹果在 iOS 7 中广泛使用了虚化效果,却没有给出接口,民间大多使用 GPUImage 这个库来实现虚化效果,终于在 iOS 8 里给出了官方支持。UIVisualEffectView
支持 UIBlurEffect 和 UIVibrancyEffect 两种效果,前者是虚化内容,后者在前者的基础上再合成一个透明视图。
UIBlurEffect(虚化效果)的实现正是基于上一节提到的渲染+合成。其手法是这样:抓取用作背景的内容然后进行缩放以降低计算量,然后分别进行水平虚化和垂直虚化(翻译得可能不对,就是横着来一下,然后竖着来一下,而不是直接对整个内容进行虚化,降低计算量),最后将缩小的内容再放大到原来的尺寸进行着色。
效果有三种样式:Extra light, Light, Dark。这三种样式对性能的要求依次降低。另外 iPad 2以及 iPad 3不支持虚化(who care?)。而 UIVibrancyEffect 效果是在 UIBlurEffect 的基础上再合成一个视图。UIVibrancyEffect 效果对性能要求极高,苹果工程师建议避免对全屏使用该效果,并给出了优化性能的建议:Rasterization 和 Group Opacity。
1.Rasterization
设置 CALayer 的 shouldRasterize 属性为 YES 能够为程序触发离屏渲染(Offscreen Rendering),更新内容时能够额外渲染不在屏幕范围上的内容用作缓存;不要滥用,因为离屏渲染的缓存大小只有屏幕尺寸的 2.5 倍大;当离线渲染的内容超过 100 ms 没有使用将会被清除。应该在以下场景中才开启该属性:
- 绘制代价很大的静态内容
- 结构非常复杂的视图
要注意,离屏渲染的计算代价是很大的,与之相比,通过普通手段显示内容要廉价很多(有很多术语由于没有铺垫写出来只会更让人困惑,还是推荐看开头的文章)。只有当屏幕中的图层不变时才可以利用这个选项来优化。因此,在一般情况下,还是不要开启离屏渲染的好。
2.Group Opacity
如果一个 layer 的 opacity 值小于 1.0 并且该 layer 含有子 layer 或者有背景图像,开启groupopacity 将会触发离屏渲染。建议一直关闭该功能,将 layer 的 allowsGroupOpacity 设置为 NO。
Profiling Tool:
Xcode 套件中的 Instruments 工具估计是最没有被有效利用的工具之一,我以前就只用来查看内存占用以及泄露问题了。实际上利用 Instruments来对视图和动画性能调优是非常高效的。视图和动画的性能一旦有问题自然是非常不爽快的,相对于手工一遍遍主观调试,Instruments 能够直接指出性能不佳的部分,一目了然。
(Instruments 的文档令人发指,基本上找不到主讲工程师在视频中提到的 debug options)
主讲的工程师在介绍工具之前给出了性能调优的检查选项:
Core Animation Instrument
在 Xcode 的菜单栏中依次选择 Product->Profile 后,会启动 Instruments 工具,有多种分析和调试工具,选择 Core Animation。
根据视频内容探索了以下调试选项:
-
Color blended layers
屏幕上绿色的部分表示该处的 layer 是 opaque (不透明的),这样 GPU 在合成时就不需要考虑该 layer 下面的内容直接输出该 layer 的内容就够了;红色的部分表示该处的内容需要 blend(混合),GPU 需要将该 layer 以及该 layer 下方的内容进行混合后才能输出,工作量大。
- Color Hits Green and Misses Red
该选项与前面提到的Rasterization有关。绿色表明离屏渲染的 cache 中还有该部分的缓存,红色表示该部分的缓存已被移除。 - Color Copied Images
前面提到过,图像要被解码后才能用于显示,GPU只支持对 JPEG 和 PNG 格式,其他格式的图像需要由 CPU 来转化解码,最好放在后台中解码。
我在使用这个选项的时候未发现画面有任何变化,尝试了浏览 gif 也没有发现异样。看来需要使用这个选项的场景太少。 - Color MisalignedImages
找出对字节没有对齐的图像并进行着色。当图像尺寸与其容器 View 的尺寸不一样的时候,需要把该图像进行缩放。 - Color Offscreen-Renderd Yellow
将离屏渲染的部分标记为黄色,查找出触发离屏渲染的部分。 - Color Compositing Fast-Path Blue
将由显示硬件(原话为 display hardware)进行混合的 layers 标记为蓝色,这是个好事,因为这意味着 GPU 的工作更少。 - Flash Updated Regions
标记屏幕上正在刷新的内容为黄色。
看完这部分以后进行性能调优不用到处猜瓶颈所在了,直接使用工具查看。
OpenGL ES Driver Instruments
在 Xcode 6.3 里,这个组件是找不到的,应该是改名成 GPU Driver 了。这个组件可以用来统计大部分的运行参数:CPU 占用,视图帧率,渲染使用,设备使用以及更多参数。我开发中的 App 的帧率还没有超过 50 的,真是惨不忍睹。该组件可以回答性能优化列表「Performance Investigation Mindset」中前面三个问题。
以前很少使用 Instruments,也因为我目前做的东西很少需要使用这些工具,当然还有可能是因为不知道能做什么所以没法用,恶性循环。
View Debugging
Xcode 6 的新特性之一,可以在 Xcode 里实时查看 UI 结构了,但只支持通过 Xcode 运行的 app。相比大名鼎鼎的 Reveal 还是有不少差距的。
当然,两者的定位不一样。目前来说对于调试够用了。
选择调试工具
Case Study
案例学习这一块给出了两个常见的性能隐患:
1.阴影绘制
以前的常见代码:
CALayer *imageViewLayer = cell.imageView.layer;
imageViewLayer.shadowColor = [UIColor blackColor].CGColor;
imageViewLayer.shadowOpacity = 1.0;
imageViewLayer.shadowRadius = 2.0;
imageViewLayer.shadowOffset = CGSizeMake(1.0, 1.0)
//设置了上面这些属性后就能绘制阴影了,这样一来Core Animation 必须知道阴影的形状才能进行绘制,但这样就必须使用离屏渲染来渲染内容。(为啥,不明白)下面的方法可以避免离屏渲染。
imageViewLayer.shadowPath = CGPathCreateWithRect(imageRect, NULL)
2.圆角绘制
以往的常见代码:
CALayer *imageViewLayer = cell.imageView.layer;
imageViewLayer.cornerRadius = imageHeight / 2.0;
imageViewLayer.masksToBounds = YES;
在给出的例子里,工程师使用一个 TableView 显示一些圆形头像。(由于说到关键时刻字幕消失了我听不懂了,所以不明白为什么这里产生了离屏渲染)我的理解是,由于重用机制,被重用的 Cell 每一次出现,Core Animation 都要为 Cell 绘制圆角,这种机制导致了离屏绘制。
工程师的建议是:
- 不要对重用的 Cell 使用 mask。
- 如果做不到上一点,尝试这个方法来:
1.将 TableView 的背景设置为 solid white;
2.在缩略图上方绘制一个圆形,圆形外围为白色;
这样做减少了离屏渲染却也增加了混合两个图层的工作,但从性能上来说依然比原来好。
总结:
离屏渲染代价昂贵,尽量避免
1·利用工具 CA Instrument 来找出它们
2·知道怎么做来避免它们(上面的 shadowPath 就是一个例子)(这个比较上,还没搞清楚离屏渲染到底怎么触发的)
在不同的设备上测试性能
1·使用 OpenGL ES Driver Instrument 来观察 GPU
2·使用 Time Profiler Instrument 来观察 CPU
知道视图的结构和任何隐含的绘制成本
1·这个对于 table cells 和滚动比较重要