02-iOS 性能优化|性能调试工具Instruments-CoreAnimation使用

2,907 阅读12分钟

前言

在项目中,直接面向用户的客户端往往是一个项目的门面。因此,在项目开发建设的过程中,为了交付用户体验较佳的客户端App,保障产品交付质量。往往需要我们开发者关注客户端软件的性能指标问题。因此,我们要对应用的性能优化专题有所研究!!
我们通常关注的性能指标有:

  • 页面卡顿
  • 耗电、发热
  • 网络优化
  • 应用启动
  • 安装包瘦身

我们在开发建设项目过程中,可以粗略划分为几个阶段:开发阶段测试阶段维护阶段:

  • 开发阶段,我们要掌握性能调试性能监测的手段,从而保障,在当前稳定版本的客户端软件,有一个比较合理的性能保障;
  • 测试阶段,测试团队等若干同事往往会给我们提出一些用户体验上的反馈和建议,因此,我们需要掌握性能调试的手段,从而改造出比较符合团队要求的产品;
  • 在上线维护阶段,针对已经上线的应用,我们的开发团队要有线上性能监控的能力,从而及时收集不满足性能指标要求的业务交互场景和步骤,捕获具体问题进行分析,从而以此为依据作为有效迭代优化我们客户端的有力助力。

为此,我们本次将会用几篇文章,围绕一些常见的性能指标,去关注 如何调试、如何监测、如何改进处理问题:

一、概述

本文主要是针对 开发阶段测试阶段 这两个线下场景,围绕常见的几个性能指标要点:页面卡顿离屏渲染耗电优化App启动优化,展开来陈述如何利用Instruments工具进行性能调试的。关于相关的同一主题的其它要点,我们会在其它文章,用新的篇幅进行讨论。

二、 Instruments工具

我们前面通过一篇文章简答介绍了Instruments这个苹果官方自带的调试工具,若本篇文章是您阅读我的第一篇文章,且您对Instruments了解甚少,可以先阅读我的这篇文章先对该工具有基本的认识:Instrument简单介绍

我们通常可以右击Xcode打开Instruments工具: Xcode->Open Developer Tool->Instruments
image.png 我们还可以在Xcode打开项目的前提下,通过以下两个方式打开Instruments:

  1. 按下两个键:Command + I打开Instrument;
  2. 或者点击:Xcode->product->profile; image.png

三、Core Animation工具

1、界面

image.png

2、简介

注意这个调试必须使用真机,点击左上角的红色圆圈就会开始录制

image.png

3、在Xcode13中通过Blank自己配置一个CoreAnimation

在我打开Xcode13的Instruments的时候,我惊呼 CoreAnimation入口怎么弄不见了??? 多了SwiftUI等入口....转念一想,一定是苹果近几年推动开发者使用Swift开发,最近又强推SwiftUI,因此把以前的界面调试入口给干掉了。不过通过查询官方文档,我们得知新版的Xcode还支持自己配置调试指标。这简直太爽了!!! 现在简单介绍一下如何自己配置出一个CoreAnimation:

3.1.打开一个Blank

image.png

3.2.配置CoreAnimation

  • 我们先找一个旧的Xcode,看看 CoreAnimation长什么样子: image.png
  • 打开 blank之后的页面: image.png
  • 添加配置项:
    • Core Animation FPS
    • Time Profile
    • Thermal State image.png
    • 配置回来了!!! image.png

我们需要了解两个两个区域:

  1. 这里记录#了实时的fps数值,有些地方是0是因为屏幕没有滑动;
    • fps(frames per second),帧率。
    • 任何屏幕总是有一个刷新率,比如iphone推荐的刷新率是60Hz,也就是说GPU每秒钟刷新屏幕60次,因此两次刷新之间的间隔为16.67ms。
    • 这段时间内屏幕内容保持不变,称为一帧(frame),fps表示frames per second,也就是每秒钟显示多少帧画面
    • 对于静止不变的内容,我们不需要考虑它的刷新率,但在执行动画或滑动时,fps的值直接反映出滑动的流畅程度
  2. 调试选项

四、调试、优化

1、Color Blended Layers

(1)、图层混合

首先我们要明白像素的概念,屏幕上每一个点都是一个像素,像素有R、G、B三种颜色构成(有时候还带有alpha值)。如果某一块区域上覆盖了多个layer,最后的显示效果受到这些layer的共同影响。举个例子,上层是蓝色(RGB=0,0,1),透明度为50%,下层是红色(RGB=1,0,0)。那么最终的显示效果是紫色(RGB=0.5,0,0.5)。这种颜色的混合(blending)需要消耗一定的GPU资源,因为实际上可能不止只有两层。如果只想显示最上层的蓝色,可以把它的透明度设置为100%,这样GPU会忽略下面所有的layer,从而节约了很多不必要的运算。

(2)、调试Color Blended Layers

第一个调试选项”Color Blended Layers”正是用于检测哪里发生了图层混合,并用红色标记出来。因此我们需要尽可能减少看到的红色区域。一旦发现应该想法设法消除它。开始调试后勾选这个选项,我们在手机上可以看到如下的场景:

image.png

重要的是backgroundColor属性,如果不设置这个属性,控件依然被认为是透明的,所以我们做的第一个优化是设置控件的backgroundColor属性。

PS:如果label文字有中文,依然会出现图层混合,这是因为此时label多了一个sublayer。

2、Color Hits Green and Misses Red

(1)、光栅化

光栅化是将一个layer预先渲染成位图(bitmap),然后加入缓存中。如果对于阴影效果这样比较消耗资源的静态内容进行缓存,可以得到一定幅度的性能提升。demo中的这一行代码表示将label的layer光栅化:


label.layer.shouldRasterize = YES;

(2)、调试Color Hits Green and Misses Red

第二个调试选项是“Color Hits Green and Misses Red”,它表示如果命中缓存则显示为绿色,否则显示为红色,显然绿色越多越好,红色越少越好。

注意:光栅化的核心在于缓存的思想。我们自己动手把玩一下,可以发现以下几个有意思的现象:

  1. 上下微小幅度滑动时,一直是绿色
  2. 上下较大幅度滑动,新出现的label一开始是红色,随后变成绿色
  3. 如果静止一秒钟,刚开始滑动时会变红。

这是因为layer进行光栅化后渲染成位图放在缓存中。当屏幕出现滑动时,我们直接从缓存中读取而不必渲染,所以会看到绿色。当新的label出现时,缓存中没有个这个label的位图,所以会变成红色。第三点比较关键,缓存中的对象有效期只有100ms,即如果在0.1s内没有被使用就会自动从缓存中清理出去。这就是为什么停留一会儿再滑动就会看到红色。

光栅化的缓存机制是一把双刃剑,先写入缓存再读取有可能消耗较多的时间。因此光栅化仅适用于较复杂的、静态的效果。通过Instrument的调试发现,这里使用光栅化经常出现未命中缓存的情况,如果没有特殊需要则可以关闭光栅化,所以我们做的第二个优化是注释掉下面这行代码

//    label.layer.shouldRasterize = true 

3、Color Copied Images

(1)、颜色格式

像素在内存中的布局和它在磁盘中的存储方式并不相同。考虑一种简单的情况:每个像素有R、G、B和alpha四个值,每个值占用1字节,因此每个像素占用4字节的内存空间。一张1920*1080的照片(iPhone6 Plus的分辨率)一共有2,073,600个像素,因此占用了超过8Mb的内存。但是一张同样分辨率的PNG格式或JPEG格式的图片一般情况下不会有这么大。这是因为JPEG将像素数据进行了一种非常复杂且可逆的转化。

CPU主要处理两件事:

  • (1)把图片从PNG或JPEG等格式中解压出来,得到像素数据
  • (2)如果GPU不支持这种颜色各式,CPU需要进行格式转换

比如应用中有一些从网络下载的图片,而GPU恰好不支持这个格式,这就需要CPU预先进行格式转化。

(2)、调试Color Copied Images

第三个选项“Color Copied Images”就用来检测这种实时的格式转化,如果有则会将图片标记为蓝色。

4、Color Misaligned Images

(1)、图片大小

在项目中,我们网络请求图片,大小不一,但是展示的UIImageView有时候是固定大小。这时候我们就需要图片的缩放了。图片的缩放需要占用时间,因此我们要尽可能保证无论是本地图片还是从网络或取得图片的大小,都与其frame保持一致。

(2)、调试Color Misaligned Images

第五个选项“Color Misaligned Images”,它表示如果图片需要缩放则标记为黄色,如果没有像素对齐则标记为紫色。勾选上这个选项并进行调试,可以看到如下场景:

image.png

5、Color Offscreen-Rendered Yellow

(1)、离屏渲染

  • 离屏渲染表示渲染发生在屏幕帧缓冲区之外;
  • 离屏渲染意味着把渲染结果临时保存到额外缓冲区,等用到时再写入帧缓冲区
  • 因此相对于普通渲染更占用资源: 交换缓冲区内容、额外渲染

(2)、调试Color Offscreen-Rendered Yellow

第六个选项“Color Offscreen-Rendered Yellow”会把需要离屏渲染的地方标记为黄色,大部分情况下我们需要尽可能避免黄色的出现。离屏渲染可能会自动触发,也可以手动触发。以下情况可能会导致触发离屏渲染:

  1. 重写drawRect方法;(自动触发离屏渲染)
  2. 有mask或者是阴影(layer.masksToBounds, layer.shadow),模糊效果也是一种mask;(自动触发离屏渲染)
  3. layer.shouldRasterize = true;(手动开启离屏渲染)

开始调试并勾选“Color Offscreen-Rendered Yellow”,会看到这样的场景: image.png

可以看到tabbar和statusBar也是黄色,这是因为它们使用了模糊效果。 如果图片使用了阴影,也是黄色,这说明它也进行了离屏渲染,解决方案,在设置阴影效果的四行代码下面添加一行:

imgView.layer.shadowPath = UIBezierPath(rect: imgView.bounds).CGPath  

这行代码制定了阴影路径,如果没有手动指定,Core Animation会去自动计算,这就会触发离屏渲染。如果人为指定了阴影路径,就可以免去计算,从而避免产生离屏渲染。

设置cornerRadius本身并不会导致离屏渲染,但很多时候它还需要配合layer.masksToBounds = true使用。根据之前的总结,设置masksToBounds会导致离屏渲染。解决方案是尽可能在滑动时避免设置圆角,如果必须设置圆角,可以使用光栅化技术将圆角缓存起来:

1. // 设置圆角
2. label.layer.masksToBounds = true  
3. label.layer.cornerRadius = 8  
4. label.layer.shouldRasterize = true  
5. label.layer.rasterizationScale = layer.contentsScale 

6、Color Compositing Fast-Path Blue

(1)、快速路径

离屏渲染的最后一步是把此前的多个路径组合起来。如果这个组合过程能由CPU完成,就会大量减少GPU的工作。这种技术在绘制地图中可能用到。

(2)、调试Color Compositing Fast-Path Blue

第七个选项“Color Compositing Fast-Path Blue”用于标记由硬件绘制的路径,蓝色越多越好。

7、Flash updated Regions

(1)、变化区域

刷新视图时,我们应该把需要重绘的区域尽可能缩小。对于未发生变化的内容则不应该重绘。

(2)、调试Flash updated Regions

第八个选项“Flash updated Regions”用于标记发生重绘的区域。

五、总结

1、避免图层混合

  • ①、确保控件的opaque属性设置为true,确保backgroundColor和父视图颜色一致且不透明;
  • ②、如无特殊需要,不要设置低于1的alpha值;
  • ③、确保UIImage没有alpha通道;

2、避免临时转换

  • ①、确保图片大小和frame一致,不要在滑动时缩放图片;
  • ②、确保图片颜色格式被GPU支持,避免劳烦CPU转换;

3、慎用离屏渲染

  • ①、绝大多数时候离屏渲染会影响性能;
  • ②、重写drawRect方法,设置圆角、阴影、模糊效果,光栅化都会导致离屏渲染;
  • ③、设置阴影效果是加上阴影路径;
  • ④、滑动时若需要圆角效果,开启光栅化;

总结

本文 简单介绍了 项目 性能调试工具 Instruments 的基本使用,目前只对CoreAnimation调试这一块 做了简单介绍 。
接下来会用几篇文章,围绕几个常见的性能问题,展开对 性能调试工具 Instrument 的 其它模块的使用介绍。

相关系列文章

Instruments