美文网首页Fuck iOS EveryDay
001性能优化-02-界面优化.md

001性能优化-02-界面优化.md

作者: 修_远 | 来源:发表于2020-06-16 10:09 被阅读0次

    [TOC]

    基础优化方向

    1. 使用 ARC 管理内存

    2. 在正确的地方使用 reuseIdentifier

      • tableview 用 tableView:cellForRowAtIndexPath: 为 rows 分配 cells 的时候,他的数据应该重用自 UITableViewCell
    3. 尽量将 views 设置为不透明(Opaque)

    4. 避免过于庞大的 XIB

    5. 不要阻塞主线程

      • 用于不要是主线程承担过多。因为UIKit在主线程上做所有工作,

      • 渲染、管理触摸反应、回应输入等都需要在主线程上完成

      • 大部分阻碍主线程的情形是你的APP在做一些涉及到读写外部资源的 I/O 操作,比如存储或者网络:

        image
    1. 不要在 viewWillAppear 中做费时的操作

      • viewWillAppear,在 view 显示之前被调用,处于效率考虑,方法中不要处理复杂费时操作。
      • 只能在该方法中设置 view 的显示属性之类的简单事情,例如背景色、字体等。否则,会明显感觉到 view 有卡顿或者延迟。

    图片处理

    1. 在 ImageView 中调整图片大小。

    2. 保证图片大小和 UIImageView 大小相同。

      如果要在 UIIImageView 中显示一个来自 bundle 的图片,你应保证图片的大小和 UIImageView 大小相同。

    3. 在运行中缩放图片是耗费资源的,特别是 UIImageView 嵌套在 UIScrollView 中的情况下。

    4. 处理网络图片。如果图片是从远端服务器加载的,你无法控制图片大小。那么就在下载完成之后,最好是在 background thread中,缩放一次,然后在 UIImageView 中使用缩放之后的图片。

    • imageNamed 初始化:加载图片、并缓存

      • imageNamed 默认加载图片成功后,内存中会缓存图片。
      • 这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象。
      • 如果缓存中没有找到响应的图片对象,则从指定地方加载图片,然后缓存对象,并返回这个而图片对象。
    • imageWithContentsOfFile 初始化

      • imageWithContentOfFile 则仅只加载、不缓存。
      • 如果加载一张大图并且使用一次,用 imageWithContentsOfFile 是最好的,这样 CPU 不需要做缓存,可以节约时间。
    • 具体使用哪一种需要根据应用场景加以区分,UIImage 虽小,但是是使用非常多的元素,也会有比较显著的问题。

      • “不以善小而不为,不以恶小而为之”
      • “不积跬步无以至千里”
    • UIImageView

      • 在性能的范围之内,直接对 UIImageView 设置圆角是不会触发离屏渲染的,但同时给 UIImageView 设置背景色肯定会触发离屏渲染
      • 触发离屏渲染跟 .png.jpg 格式无关

    UITableView 优化

    view相关操作

    • 正确使用 reuseIdentifier 来重用 cells
    • 减少subview的数量
    • 尽量使所有的 view opaque(不透明),包括cell自身
    • 使用 shadowPath 来画阴影
    • 避免渐变、图片缩放

    缓存相关操作

    • 缓存行高
    • 尽量不要使用 cellForRowAtIndexPath。如果要使用,只用一次,然后缓存结果
    • 尽量使用 rowHeight、sectionHeight、sectionHeaderHeight 来设定固定的行高,不要请求delegate

    数据加载相关操作

    • 如果 cell 内实现的内容来自 web,使用异步加载,缓存请求结果
    • 使用正确的数据结果来存储数据

    CPU 和 GPU 层优化

    本质上是降低 CPU、GPU的工作,从这两个方面入手去提升性能

    • CPU做了什么事

      • 逻辑运算
      • 对象的创建
      • 对象属性的调整、布局计算、文本的计算和排版
      • 图片的格式转码和解码
      • 图像的绘制
    • GPU做了什么事

      • 数学运算
      • 纹理绘制

    CPU 层面的优化

    • 尽量用轻量级的对象,比如用不到时间处理的地方,可以考虑使用 CALayer 取代 UIView
    • 不要频繁调用 UIView 的相关属性,例如 frame、bounds、transform 等属性,尽量减少不必要的修改
    • 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要对此修改属性
    • Autolayout 会比直接设置 frame 消耗更多的 CPU 资源
    • 图片的 size 最好刚好更 UIImageView 的 size 保持一致
    • 控制一下 线程的最大并发数量
    • 尽量把耗时操作放到子线程
      • 文本处理(尺寸计算、绘制)
      • 图片处理(解码、绘制)

    GPU 层面的优化

    • 尽量避免短时间内大量图片的显示,尽可能你将多张图片合成一张进行显示
    • GPU 能处理的最大纹理尺寸是 4096x4096,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以纹理尽量不要超过这个尺寸
    • 尽量减少视图数据和层次
    • 减少透明的视图(alpha<1),不透明的就设置 opaque 为 YES(打开绘制优化开关)
    • 尽量避免出现离屏渲染

    离屏渲染优化

    如何高性能的画一个圆角?

    视图和圆角的大小对帧率并没有什么影响,数量最核心的影响因素

    image

    这个是我们最常规的设置方式,但不可取,因为会触发离屏渲染

    • 如果能够只用 cornerRadius 解决问题,就不用优化
    • 如果必须设置 maskToBounds,可以参考圆角视图的数量,如果数量少(一页只有几个)也可以考虑不用优化。
    • UIImageView 的圆角通过直接截取图片实现,其他视图的圆角可以通过 Core Graphics 画出圆角矩阵实现
    • 通过 CoreGraphic 画一个圆角,不会触发离屏渲染
    image

    什么是 “离屏渲染”?

    离屏渲染就是在当前屏幕缓冲区外,新开辟一个缓冲区进行操作。

    • 在 OpenGL 中,GPU 有2中渲染方式

      • On-Screen Rendering:当前屏幕渲染,在当前用于显示的屏幕缓冲区进行渲染操作
      • Off-Screen Rendering:离屏渲染,在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作

    为何要避免离屏渲染?

    CPU、GPU 在绘制渲染视图是做了大量的工作。离屏渲染发生在 GPU 层面上,会创建新的渲染缓缓冲区,会触发 OpenGL 的多通道渲染管线,图像上下文切换会早晨额外的开销,增加 GPU 工作量。如果CPU、GPU累计耗时 16.67 ms还没有完成,就会造成卡顿掉帧。

    圆角属性、蒙层遮罩 都会触发离屏渲染。指定以上属性,标记了它在新的图形上下文中,在未愈合之前,不可以用于显示的时候就触发了离屏渲染。

    离屏渲染消耗性能的原因?

    • 需要创建新的缓冲区

    • 离屏渲染的整个过程,需要多次切换上下文环境

      • 上下文先从当前屏幕(On-Screen)切换到离屏(Off-Screen)
      • 等离屏渲染结束后,将离屏缓冲区的结果显示到屏幕上
      • 再将上下文切换到当前屏幕

    哪些操作会触发离屏渲染?

    1. layer.shouldRasterzize:光栅化

      • 光栅化概念:将图转化为一个个栅格组成的图像
      • 光栅化特点:每个元素对应帧缓冲区的一像素
      • 光栅化限制:系统给光栅化限制了内存,如果超过就会触发离屏渲染,所以cell中一般不使用
    2. mask:遮罩

    3. shadows:layer.shadowXXX,如果设置了 layer.shadowPath 就不会产生离屏渲染

    4. group opacity:不透明,layer属性

    5. edge antialiasing:抗锯齿

    6. cornerRadius:圆角。同时设置 layer.masksToBounds = YES; layer.cornerRadius 大于0,考虑通过 CoreGraphics 绘制圆角,或者让美工提供圆角图片

    7. 渐变

    8. drawRect

    离屏渲染 VS CPU 渲染

    上面说到了,所有不在 GPU 的当前屏幕缓冲区进行的渲染都叫离屏渲染,还有另外一种特殊的离屏渲染,叫“CPU 渲染”。

    • CPU渲染:

      • 如果重写了 drawRect 方法,并且使用任何 Core Graphics 的技术,进行了绘制操作,就涉及到了 CPU渲染
      • 整个渲染过程由 CPU 在 APP 内同步完成,渲染得到的 bitmap 最后交给 GPU 用于显示

    由于 GPU 的浮点运算能力比 CPU 强,CPU 渲染的效率可能不如离屏渲染。但如果只是实现一个简单的效果,直接使用CPU渲染就可以,因为离屏渲染涉及到缓冲区创建以及上下文切换等耗时操作。

    如何检测离屏渲染?

    1. 模拟器:Debug->Color Off-screen Rendered

      image image image

      可以看到图片亮黄色部分就是离屏渲染的视图

    2. 真机:xcode9之后可以不用 Instrument 了,运行程序之后:Debug -> View Debugging -> Rendering -> Color Offscreen-Rendered Yellow

      设置路径 image

    离屏渲染的解决思路

    1. 预排班,提前计算

      在接收到服务器端返回的数据后,尽量将 CoreText 排版的结果、单个空间的高度、cell 整体的高度提前计算好,将其存储在模型的属性中。需要使用时,知己耳聪模型中往外取,避免了计算的过程。

      尽量少用 UILabel,可以使用 CALayer。避免使用 AutoLayout 的自动布局,才去纯代码的方式

    2. 预渲染,提前绘制

      圆形的图标可以提前在:接收到网络返回数据时,后台线程进行处理,直接存储在模型数据中,回到主线程后直接调用

      避免使用 CALayer 的 Border、Corner、Shadow、Mask 等技术,这些都会触发离屏渲染

    3. 异步绘制

    4. 全局并发线程

    5. 高效的图片异步加载

    图层混合优化

    1. 怎么检测图层混合

      在下面 instrument 工具介绍中有给出

    2. 怎么避免图层混合

      • 确保控件的 Opaque 属性设置为 true
      • 确保控件的 背景色 和父视图的背景色一致 且不透明
      • 如无特殊需要,不要设置低于 1 的 alpha 值
      • 确保 UIImage 没有alpha 通道
      • UILabel iOS8以后设置背景色为非透明 和 设置 label.layer.masksToBounds=YES,就可以让label只渲染给定的size区域,解决 UILabel 的图层混合问题

    instrument 工具

    上面讲到了如何检测离屏渲染,除了离屏渲染 Rendering 还有其他几种调试类型

    • Color Blended Layer :图层混合

      表示区域使用多种混合图层,(图层混合:由于多 UI/Layer 叠加,如果有透明或半透明颜色时,CPU就会去计算最终显示的颜色,中间就涉及很很多多余计算。

      颜色标识

        - 红色:混合图层
        - 绿色:没有使用混合
      

      调优

        减少红色区域
        
        1. 设置 Opaque 属性为 YES
        2. 给 view 设置一个不透明的颜色
      
      image
      image
    • Color Hits green and Misses Red:光栅化(缓存layer)

      检测 layer 是否使用 shouldRasterize,为true开启光栅化(默认),光栅化会将layer预先渲染为位图 bitmap,然后缓存,从而提高性能。

      颜色标识

        - 红色:光栅化
        - 绿色:未光栅化
      

      调优

        使用内容不变的layer,不适合tableview,会造成多余离屏渲染降低性能(原因:系统给光栅化限制了内存,如果超过就会离屏渲染)
      
      image
    • Color copied Images 图片格式检测与复制

      Shows images that are copied by Core Animation in blue,苹果官方注释说 被拷贝给CPU进行转化的图片显示为绿色。

      如果 GPU 不支持当前图片的颜色格式,那么就会将图片交给 CPU 预先进行格式转化,并且这张图片标记为绿色。

      GPU 只解析 32 bit 的颜色格式,如果使用 Color Copied Images 调试,图片是蓝色

      颜色标识

        - 蓝色:需要赋值
      

      扩展

         32bit 指图片颜色深度,用“位”来表示,用来表示显示颜色数量,例如一个图片支持256种颜色,那么就需要256个不同的值来表示不同的颜色,就需要0-255,二进制表示就是从 00000000-11111111,一共需要 8位 二进制数,所以**颜色深度是 8**。通常32bit色彩中使用3个bit表示R(红)G(绿)B(蓝),还有一个 8bit 表示 Alpha(透明度)
      
    • Color misaliged Images:图片尺寸匹配

      目标像素与源像素不对齐的图片,比如图片大小和 UIImageView 大小不一致

      颜色标识

        - 洋红色:图片没有像素对齐
        - 黄色:图片缩放
      

      优化

        尽量匹配大小
      
    • Color Compositing Fast-Path Blue:快速路径

      标记由硬件绘制的路径,显示蓝色,越多越好。可以直接对OpenGL绘制的图像高亮。

      颜色标识

        - 蓝色
      

      优化

        一般不做检测
      
    • Flash updated Regions:重绘区域

      对重绘区域高亮为黄色,会使用 CoreGraphic 绘制,越小越好

      颜色标识

        - 黄色
      
    • Color Immediately:颜色刷新频率

      当执行颜色刷新的时候移除 10ms 的延迟,因为可能在特定情况下你不需要这些延迟,所以使用此选项加快颜色刷新的频率。

      一般用不到

    opaque 绘制优化

    这个值不是决定视图是否是透明的,而是给绘制系统提供一个个性能优化的开关

    • YES:不透明。UIView 默认为 YES
    • NO:透明。UIButton、UILabel等子类默认为 NO

    red + green = yellow

    image

    在第一篇文章中介绍了一个公式:

    R = S + D * (1 - Sa)

    - R:像素RGB
    - S:源色彩(顶端纹理)
    - D:目标颜色(低一层的纹理)
    - Sa:源色彩的透明度
    
    • redView = (1, 0, 0, 1)
    • greenView = (0, 1, 0, 0.5)

    R = 1 + 0 * (1-0.5)
    G = 0 + 1 * (1-0.5)
    B = 0 + 0 * (1-0.5)
    A = 1 + 0.5 * (1-0.5)

    • 得出结果是:(1, 0.5, 0, 1)

    而当 Sa=1 时,R=S,也就是说。这个时候不管目标颜色是什么,GPU 都不需要做任何计算合成。只需要简单的从这个层进行拷贝,节省了GPU大量的工作量。

    相关文章

      网友评论

        本文标题:001性能优化-02-界面优化.md

        本文链接:https://www.haomeiwen.com/subject/pyohxktx.html