iOS app性能优化的那些事(二)

作者: 树下老男孩 | 来源:发表于2015-09-09 15:06 被阅读13097次

    这次我们来说说iOS app中滑动的那些事。iOS为了提高滑动的流畅感,特意在滑动的时候将runloop模式切换到UITrackingRunLoopMode,在这个过程中专心做跟滑动相关的工作,这也就是在滑动过程中为什么nstimer无法工作的原因,因为两个没在同一mode下面。但我们可能经常会遇到滑动不怎么流畅的情况,比如在项目中碰到在滑动tableview的时候不怎么顺畅,感觉有点不爽,即便是在测试中表现最好的5s(touch之类的感受更直观)。

    tableview 滑动不流畅

      那碰到这种情况该怎么处理,分析图像动画性能主要用的是Core Animation这个组件,先简单介绍一下里面一些经常用到的选项:

    • Color Blended layers
      标示混合的图层会为红色,不透明的图层为绿色,通常我们希望绿色的区域越多越好。
    • Color Hits Green and Misses Red
      假如我们设置viewlayer的shouldRasterize为YES,那些成功被缓存的layer会标注为绿色,反之为红色,下面会有详细介绍。
    • Color copied images
      标示那些被Core Animation拷贝的图片。这主要是因为该图片的色彩格式不能被GPU直接处理,需要在CPU这边做转换,假如在主线层做这个操作对性能会有一定的影响。
    • Color misaligned images
      被缩放的图片会被标记为黄色,像素不对齐则会标注为紫色。
    • Color offscreen-rendered yellow
      标示哪些layer需要做离屏渲染(offscreen-render)。

    简单介绍完Core Animation的一些东西之后我们回过头来看看哪些问题会影响到图形的性能,下面这张图摘自WWDC2014(Advanced Graphics and Animations for iOS Apps,这上面的一些分享非常有技术性)

    performance investigation mindset.png

    当你碰到性能问题的时候,你可以思考一下:

    是否受到CPU或者GPU的限制?
    是否有不必要的CPU渲染?
    是否有太多的离屏渲染操作?
    是否有太多的图层混合操作?
    是否有奇怪的图片格式或者尺寸?
    是否涉及到昂贵的view或者效果?
    view的层次结构是否合理?
    

    那么哪些是你最该开始考虑的方向呢?通常发生图形性能问题的时候,比如列表滑动不顺畅、动画卡顿等,大部分都是由于Offscreen Rendering(离屏渲染)或者blending导致的,因为这在动画的每一帧都会涉及到。

    offscreen-render

    什么是offscreen-render?offscreen-render涉及的内容比较多,有offscreen-render那就有onscreen render,onscreen render指的是GPU在当前用于显示的屏幕缓冲区进行渲染,相反offscreen-render就是不在当前的屏幕缓存区,而在另外的缓冲区进行渲染,offscreen-render有两种形式:

    CPU的offscreen-render

    使用CPU来完成渲染操纵,通常在你使用:

    • drawRect (如果没有自定义绘制的任务就不要在子类中写一个空的drawRect方法,因为只要实现了该方法,就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以 contentsScale的值,造成资源浪费)
    • 使用Core Graphics
      上面的两种情况使用的就是CPU离屏渲染,首先分配一块内存,然后进行渲染操作生成一份bitmap位图,整个渲染过程会在你的应用中同步的进行,接着再将位图打包发送到iOS里一个单独的进程--render server,理想情况下,render server将内容交给GPU直接显示到屏幕上。
    GPU的offscreen-render

    使用GPU在当前屏幕缓冲区以外开辟一个新的缓冲区进行绘制,通常发生的情况有:

    • 设置cornerRadius, masks, shadows,edge antialiasing等
    • 设置layer.shouldRasterize = YES
    渲染流程

    offscreen-render对性能到底有什么影响?通常大家说的离屏渲染指的是GPU这块(当然CPU这块也会有影响,也需要消耗一定的资源),比如修改了layer的阴影或者圆角,GPU需要做额外的渲染操作。通常GPU在做渲染的时候是很快的,但是涉及到offscreen-render的时候情况就可能有些不同,因为需要额外开辟一个新的缓冲区进行渲染,然后绘制到当前屏幕的过程需要做onscreen跟offscreen上下文之间的切换,这个过程的消耗会比较昂贵,涉及到OpenGL的pipeline跟barrier,而且offscreen-render在每一帧都会涉及到,因此处理不当肯定会对性能产生一定的影响,所以可以的话尽量减少offscreen-render的图层,查看哪些图层需要离屏渲染可以用Instruments的Core Animation工具进行检测,Color Offscreen-Rendered Yellow选项会将对应的图层标记为黄色。

    Blending

    假如最上层的view是不透明的,那直接使用这个view的对应颜色之就可以,但如果view是透明的,在计算像素的颜色值时就需要计算它下面图层,透明的视图越多,计算量就越大,因此也会对图形的性能产生一定的影响,所以可以的话也尽量减少透明图层的数目。

    Demo

    下面给出一个简单demo的优化过程,这个demo里面涉及到的问题是在实际项目中所碰到的,也就是最上面那张图里列表滑动不流畅情况---由阴影以及圆角导致的offscreen-render。
    整个页面就是一个简单的tableview,其中头像为圆角,一个label有阴影效果,滑动的时候在iPod上帧率只有可怜的28FPS。

    Color Offscreen-Rendered Yellow
    28FPS.png

    其中黄色的区域就是离屏渲染的地方,也就是含有圆角跟阴影的layer。

    shadowPath

    设置label的阴影效果可以通过:

        cell.sign.layer.shadowOffset = CGSizeMake(0, 2);
        cell.sign.layer.shadowOpacity = 0.5;
        cell.sign.layer.shadowColor = [UIColor blackColor].CGColor;
    

    但是你可以发现这会导致离屏渲染,一个简单的不需要离屏渲染的方法就是制定阴影的路径,也就是设置layer的shadowPath属性,通过instruments发现阴影的地方没有黄色了,帧率也提高到了40FPS:

    cell.sign.layer.shadowPath = [UIBezierPath  bezierPathWithRect:cell.sign.bounds].CGPath;
    
    设置shadowPath消除离屏渲染.png
    rasterize

    对于圆角这种类似导致的性能问题,最简单的就是在列表中不要使用圆角,假如要使用圆角的话,一种最快提升性能的方式就是设置layer的shouldRasterize为YES:

       cell.layer.shouldRasterize = YES;
       cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
    

    虽然被Rasterize的图层也会引起离屏渲染,如下图所示,整个cell都被标示为黄色:


    shouldRasterize.png

    layer设置shouldRasterize=YES之后,会把被光栅化的图层保存成位图并缓存起来,其中圆角或者阴影之类的效果也是直接保存到位图当中,当需要渲染到屏幕上的时候只需要到缓存中去取对应的位图进行显示就行了,加快了整个渲染过程。可以通过勾选instruments core animation中的Color Hits Green and Misses Red选项来查看图层是否被缓存了,如果图层显示为绿色则表示已经被缓存起来了,也就是这个缓冲区的内容被复用了,不用在去重新创建缓冲区,反之则是用红色标示。如下图可以看到设置shouldRasterize之后,cell都被标示为绿色了,如果滑动过程中发现都是红色的证明就有问题了:

    cached.png

    再看看现在滑动的帧率:


    优化后.png

    可以发现现在滚动的性能大大提高了,光栅化对于那些有很多子view嵌套在一起、view的层级复杂或者有很复杂特效效果的图层有很明显的提升,因为这些内容都被缓存到位图当中了。但是使用光栅化需要注意一些内容:

    • 适用于内容基本不变的图层
      假如图层的内容经常变化,比如cell里面有涉及到动画之类的,那么缓存的内容就无效了,GPU需要重新创建缓存区,导致离屏渲染,这又涉及到OpenGL的上下文环境切换,反而降低性能。
    • 不要过度使用
      缓存区的大小被设置为屏幕大小的2.5倍,假如过分使用同样会导致大量的离屏渲染。
    • 如果缓存的内容超过100ms没有被使用则会被回收。

    tips

    • 对于圆角可以使用一张中间圆形透明的图覆盖在上面,虽然这会引入blending操作,但是大部分情况下性能会比离屏渲染好。
    • 让你的view层次结构平坦一些,因为OpenGL在渲染layer的时候,在碰到有子层级layer的时候可能需要停下来把两者合成到一个buffer里再接着渲染。(When the OpenGL renderer goes to draw each layer, it may have to stop for some subhierarchies and composite them into a single buffer).
    • 延迟加载图片
      有时候在边滚动边设置图片的时候可能会有一定的影响,因此可以在滚动的时候imageview不执行setimage的操作,滚动停止的时候才加载图片,由于滚动的时候NSRunloop是处于UITrackingRunLoopMode模式下,可以采用如下的方式,将设置图片放到NSDefaultRunLoopMode模式下才进行:
    UIImage *downloadedImage = ...;
        [self.avatarImageView performSelector:@selector(setImage:)
                                   withObject:downloadedImage
                                   afterDelay:0
                                      inModes:@[NSDefaultRunLoopMode]];
    

    图形性能这块有什么好的想法也可提出来交流一下~~

    参考:

    相关文章

      网友评论

      • 栈溢出:cell.sign.layer.shadowPath = [UIBezierPath bezierPathWithRect:cell.sign.bounds].CGPath;

        模拟器会奔溃,能否告知下
        树下老男孩:@栈溢出 啥错误
      • AmateuZero:(不知道为何您的上一篇文章找不到了, 只好来这里提问...)
        您在上一篇种性能优化技巧第八条提到了lua优化, 十分感兴趣, 请问是否有Demo供我等菜鸟学习一下? 我百度了一下, 有关lua在iOS中的应用主要是LuaBinding和阿里的那个wax, 并没有找到类似的相关文章, 您可否就使用技巧和使用使用场景点拨一二?
        树下老男孩:@AmateuZero lua这个主要用的是内部实现的一套框架,你上面提到的这两个倒是没怎么研究,不过最近在研究react
      • __微凉:你好,有个问题想请教一下.延迟加载图片在什么场合下可以用到?在tableView中,由于循环利用的关系,如果不在滑动的过程中设置图片,可能显示不了正确的图片
        树下老男孩:@__微凉 现在微信滑动的时候不会播放表情动画,可能这边可以用到,那个只是举个例子,哈哈
      • 公爵海恩庭斯:其实我觉得如果有圆角矩形这种需求,资源服务器直接处理成圆角是比较灵活的做法。将来如果需求变更了,只需要在后端进行修改,就能调整手机端的样式。
        公爵海恩庭斯:@树下的老男孩 无所谓了,如果后端同学实在不愿意改,移动端一样可以完成任务。这是问题边界问题,不讨论了。
        公爵海恩庭斯:@树下的老男孩 那需求变更就不得不重新发版了。从问题复杂度来讲,一张图片在服务器端直只需要处理一次,但是如果逻辑放在移动端,每一个机器上都需要处理一次。
        树下老男孩:@公爵海恩庭斯 这种就的增加服务端的业务复杂度,需要考虑各种机型屏幕,还是客户端来处理好点
      • 公爵海恩庭斯:干货,不错。我觉得圆角图形的处理方式还有一些,可以写一个 list 放在小节的最前面。1. layer的shouldRasterize为YES;2. 圆形遮罩;3. 服务器处理后直接下发圆形图 or 本地处理成圆形图片缓存在本地(使用 SDWebImage 框架下载,但是要用处理后的圆形图片覆盖原始缓存)。

      本文标题:iOS app性能优化的那些事(二)

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