美文网首页
iOS view绘制性能

iOS view绘制性能

作者: 只写BUG | 来源:发表于2017-07-26 09:13 被阅读0次

    视图显示到屏幕的过程:

    CPU将显示的视图数据计算好,然后传给GPU渲染,渲染结束后,将数据传递给帧缓冲区,等待显示器绘制在屏幕上。
    CPU计算:比如设置frame等,绘制一个layer
    GPU渲染:将view的多个layer合成,再合成成像素的数据。GPU 能干的事情比较单一:接收提交的纹理(Texture)和顶点描述(三角形),应用变换(transform)、混合并渲染,生成bitmap.

    iOS设置View到其显示在屏幕的过程,可以分成两个阶段:

    App进程内:

    • 布局:设置整个view tree的hierarachy,和view frame参数
    • 生成backing image:比如有些view设置了image background或者是imageview 需要加载图片资源等操作。以及重写了view drawRect方法,使用core graphics绘制生成image等。
    • 准备:设置动画的layer参数。
    • 提交:将以上所生成的数据,打包序列化传递给render 进程。

    渲染进程

    • 反序列传递需要绘制的数据,生成view tree
    • 将数据合成bitmap,放入帧缓冲区。

    影响绘制性能的关键问题

    • view tree 中layer越多,视图的shape复杂

    视图的frame计算都是需要cpu上计算的,所以视图层次越多越复杂多将消耗更多的cpu资源

    GPU渲染两种方式

    当前屏幕渲染:GPU的渲染操作用于当前的屏幕缓冲区
    离屏渲染:指在GPU渲染之外开辟一个缓冲区,进行渲染操作。

    只不过,将不在GPU缓冲渲染的操作都统称为离屏渲染:CPU渲染
    比如常见重写drawRect操作就是离屏渲染方式。生成的bitmap传递给GPU用于显示。
    设置以下属性的时候,都会触发离屏渲染
    设置shouldRasterize(光栅化):每一个元素对应帧缓冲区中的一像素,用于缓冲相同的帧; masks,shadows;设置圆角,渐变等

    • 像素过渡绘制
      因为一个像素被多个view覆盖,view可能设置了透明度。
      像素的计算方式 new =old alpha+ v (1- alpha)
      old表示现有的纹理(rgb)的值,v表示添加的view的color,透明度为alpha,new表示叠加v之后的纹理值。

    比如:old = (100,100,100),v = (50,50,50),alpha = 0.5,那么new = (75,75,75)
    如果太多计算的话,可能在1/60s内无法完成计算,就有可能出现掉帧的现象,掉帧到一定程度就可能出现屏幕卡顿的情况。

    • 生成backing image
      使用 image named:path方法,加载bundle下面的资源,并解压图片文件,这个过程也需要消耗一定的CPU资源。其他的比如从网上下载的资源,需要显示的时候才进行解压。

    Demo

    使用view来显示一个矩形,通过以下三种不同的方式来测试每种方法的代价,主要是内存消耗上考虑。

    • 重写UIView的drawRect方法
    • 调用core graphics 生成bitmap image,通过作为UIImageView的显示内容,然后添加到view的subview
    • 通过新建CAShapeLayer添加,将绘制的矩形在CAShapeLayer上面,然后添加到View.layer中
      因为这三种结果之间的差异变得更明显,所以我种方法都做了100次
      最后,结果如下:
    • drawRect的方法,内存一个view大概12M,100个1200M
    • 和第一种结果一样的
    • 使用CAShapeLayer添加的,最后内存也基本上没有变化。
      那么,得出结论,第一种和第二种最耗内存,最后那种基本不怎么耗内存。

    结果分析

    每一个view实例,都默认管理着一个layer。UIView主要负责用户的交互,CALayer就是绘制图层属性和图形数据,用于视图的渲染。CALayer也具有层级关系,和UIView不同的是,它不知道响应链的存在。UIView其实是CALayer一层封装,让用户更加关注视图的处理逻辑,而不是视图的绘制逻辑,但是当你遇到性能问题的时候,你也不得不去了解更加深层的结构。
    UIView实现了CALayer的CALayerDelegate,view视图的-drawRect方法背后其实调用了CALayer进行重绘和保存中间图片,当调用以下方法

    • (void)displayLayer:(CALayer *)layer;
    • (void)drawLayer:(CALayer )layer inContext:(CGContextRef)ctx;
      先调第一个方法,如果UIView中,重写了该方法,则直接设置给寄宿图contents,如果没有实现那个,尝试调第二个方法,绘制图image。那么
      在后面这个过程中,layer会创建一个合适尺寸的bitimage, 传入上下文的大小size * scale。如果是retina屏的话,1028
      768*4 = 12M左右,所以内存开销非常大。其中,这个上下文ctx,我们并没显示传递过来,因为UIKit会维护隐式的上下文ctx栈。

    回到实验部分,第一种方法,重写了drawRect,在数据准备阶段,生成该view的layer的contents的时候,需要在内存中view一样大的bitmap,并且这部分内存,只要view需要被显示在屏幕上,就一直不被释放。这就是实验中第一种方法,内存一直居高不下的原因。
    第二种方法,内存先增加,是因为通过到创建core graphics 生成image,同样需要另外创建的内存,最后保留生成的bitmap在内存中.
    最后一种方法,通过添加CAShapeLayer到view.layer的方法,其绘制图形的工作完全交由GPU来完成,所以不需要额外的内存。

    参考:
    https://objccn.io/issue-3-1/
    http://www.jianshu.com/p/a1f575709e7c

    相关文章

      网友评论

          本文标题:iOS view绘制性能

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