• iOS开发UITableView性能优化总结


    UITableView是iOS开发中最常用的控件,UITableView性能优化也是老生常谈了,大致总结如下,以供参考

    1.    把赋值和计算布局以及数据绑定分离

        UITableView最核心的思想就是UlITableViewCell的重用机制。简单的理解就是: UlTableView只 会创建一屏幕(或一屏幕多一点)的UITableViewCell,其他都是从中取出来重用的。每当Cell滑出屏幕时,就会放入到一个集合(或数组)中(这里就相当于一个重用池),当要显示某一位置的Cell时,会先去集合(或数组)中取,如果有,就直接拿来显示;如果没有,才会创建。这样做的好处可想而知,极大的减少了内存的开销。

          思路是把赋值和计算布局分离。这样让tableView:cellForRowAtIndexPath:方法只 负责赋值,tableView:heightForRowAtIndexPath:方法只负责计算高度。注意:两个方法尽可能的各司其职,不要重叠代码!两者都需要尽可能的简单易算。Run一下,会发现UITableView滚动流畅 了很基于.上面的实现思路,我们可以在获得数据后,直接先根据数据源计算出对应的布局,并缓存到数据源中,这样在tableView:heightForRowAtIndexPath:方 法中就直接返回高度,而不需要每次都计算了。再一个就是我们经常在注意cellForRowAtIndexPath:中为每一.个cel绑定数据,实际上在调用.cellForRowAtIndexPath:的时候cell还没有 被显示出来。

    (1)willDisplayCell

    为了提高效率我们应该把数据绑定的操作放在cell显示出来后再执行,可以在tableView: willDisplayCell: forRowAtIndexPath: (以后简称willDisplayCell)方法中绑定数据。注意illDisplayCell在cell在tableview展示之前就会调用,此时cell实例已经生成,所以不能更改cell的结构,只能是改动cell 上的UI的一些属性(例如label的内容等) 。

    (2)定义高度
    1>新建一. 个继承自UITableViewCel的类
    2>重写initWithStyle:reuseldentifier:方法
    3>添加所有需要显示的子控件(不需要设置子控件的数据和frame,子控件要添加到contentView中)
    4>进行子控件一次性的属性设 置(有些属性只需要设置一次, 比如字体固定的图片)
    5>提供2个模型
    数据模型:存放文字数据图片数据.
    frame模型:存放数据模型所有子控件的framecell的高度
    6>cell拥有一个frame模型(不要 直接拥有数据模型)
    7>重写frame模型属性的setter方法:在这个方法中设置子控件的显示数据和frame
    (3)自定义高度原理
    A手动计算
    1>由于heightForRow比kcellForRow方法先调用,创建frame模型包含微博模型,重写微博模型赋
    值set方法,提前计算cell子控件的frame并保存,heightForRow方 法中取出frame模型中保存的高
    度,实现自定义高度cll
    2>设置最大尺寸、文本属性,根据文本内容计算正文内容展示尺寸
    3> cellForRow中创建自定义cell包含frame属性,重写frame属性set方法创建cell子控件并赋值
    frame模型保存的子控件尺寸
    B.自动计算
    1>首先设置行高使用autolayout自动计算并预估高度
    2>在stroboard中对cel内容进行自动布局,注意设置图片距离底部约束,cellForRow中 创建
    storyboard中对应标记的自定义cell
    3>由于正文内容的不确定性,设置label多行,拖线图片高度约束,根据图片有无,设置代码设
    置高度约束

    2 自定义Cell的绘制

           上面的改进方法并不是最佳方案,但基本能满足简单的界面!记得开头我的任务吗?像朋友圈那样的图文混排,这种方案还是扛不住的!我们需要进入更深层次的探究:自定义Cell的绘制。我们在Cell.上添加系统控件的时候,实质上系统都需要调用底层的接口进行绘制,当我们大量添加控件时,对资源的开销也会很大,所以我们可以索性直接绘制,提高效率。首先需要给自定义的Cell添加draw方法,(当 然也可以重写drawRect)然后在方法体中实现:

    (1) TableView渲染

    为了保证TableView的流畅,当快速滑动的时候,cell必须被快速的渲 染出来。所以ell渲染的速度必须快。如何提高cel的渲染速度呢?当有图像时,预渲染图像,在bitmap context先将其画一 -遍, 导出成Ullmage对象, 然后再绘制到屏幕,这会大大提高渲染速度。具体内容可以自行查找“利用预渲染加速显示iOS图像”相关资料(https://blog.csdn.net/qiaoxinde/article/details/50766844)。渲染最好时的操作之一就是混 合(blending)了,所以我们不要使用透明背景,将Cel的opaque 值设为Yes, 背景色不要使用clearColor, 尽量不要使用阴影渐变等由于混合操作是使用GPU来执行,我们可以用CPU来渲染,这样混合操作就不再执行。可以在UIView的drawRect方法中自定义绘制。

    (2)减少视图的数目
    我们在cell.上添加系统控件的时候,实际上系统都会调用底层的接口进行绘制,大量添加控件

    时,会消耗很大的资源并且也会影响渲染的性能。当使用默认的UITableViewCell并且在它的ContentView.上面添加控件时会相当消耗性能。所以目前最佳的方法还是继承UITableViewCell,并重写drawRect方法。

    (3)减少多余的绘制操作

    在实现drawRect方法的时候,它的参数rect就是我们需要绘制的区域,在rect范围之 外的区域我们不需要进行绘制,否则会消耗相当大的资源。

    (4)不要给cell动态添加subView

    在初始化cell的时候就将所有需要展示的添加完毕,然后根据需要来设置hide属性显示和隐藏。

    (5)异步化UI,不要阻塞主线程

    我们时常会看到这样一个现象,就是加载时整个页面卡住不动,怎么点都没用,仿佛死机了一般。原因是主线程被阻塞了。所以对于网路数据的请求或者图片的加载,我们可以开启多线程,将耗时操作放到子线程中进行,异步化操作。这个或许每个iOS开发者都知道的知识,不必多讲

    3 滑动时按需加载对应的内容

    如果目标行与当前行相差超过指定行数,只加载滑动结束时屏幕所见的cell。

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
                    
            if loadarr.count > 0 && !loadarr.contains(indexPath) {
    //不需要加载的
                cell.firstLabel.backgroundColor = .red
            }else{
    //需要加载的
                cell.firstLabel.backgroundColor = .green
            }
        }
       
        func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
            
            //目标行
            guard let indexPath = tableView!.indexPathForRow(at: CGPoint(x: 0, y: targetContentOffset.pointee.y)) else{
                   return
            }
               
            //当前屏幕中显示的第一条
            guard  let currentIP = tableView!.indexPathsForVisibleRows?.first else{
                   return
            }
           //当滑动大于一定行数时,只加载最后目标屏幕的cell,其余的中间的要划过的cellr停止加载
            if abs(indexPath.row - currentIP.row) > 8 {
                   
                guard let arr = tableView!.indexPathsForRows(in: CGRect(x: 0, y: targetContentOffset.pointee.y,  self.view.bounds.size.width, height: self.view.bounds.size.height))
                else{
                    return
                }
                   
               loadarr.append(contentsOf: arr)
                   
            }
               
               
        }
    
        func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
            let scrollstop = !scrollView.isTracking && !scrollView.isDragging && !scrollView.isDecelerating
            if scrollstop {
    //全局变量loadarr
                loadarr.removeAll()
            }
        }
     
    4 离屏渲染问题

    1.离屏渲染的问题的造成,下面的情况或操作会弓|发离屏渲染:

    ●为图层设置遮罩(layer.mask)

    ●将图层的layer.masks' ToBounds / view.clips ToBounds属性设置为true

    ●将图层layer.allowsGroupOpacity属性设置为YES和layer.opacity小于1.0

    ●为图层设置阴影(layer.shadow*) 。

    ●为图层设置layer. shouldRasterize=true

    ●具有layer.cornerRadius, layer.edgeAntialiasingMask, layer.allowsEdgeAntialiasing的图层

    ●文本(任何种类,包括UIL abel, CATextl _ayer, Core Text等)。

    ●使用CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现

    2离屏渲染优化方案

    官方对离屏渲染产生性能问题也进行了优化: 

    ios 9.0之前UlimageView跟UlButton设置圆角都会触发离屏渲染。

    iOs 9.0之后UIButton设置圆角会触发离屏渲染,而UllmageView里png图片 设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。

    (1)圆角优化

    在APP开发中,圆角图片还是经常出现的。如果一个界面中只有少量圆角图片或许对性能没有非常大的影响,但是当圆角图片比较多的时候就会APP性能产生明显的影响。我们设置圆角一般通过如下方式:

    imageView.layer.cornerRadius=CGFloat(10);

    imageView.layer.masks ToBounds=YES;

    这样处理的渲染机制是GPU在当前屏幕缓冲区外新开辟一个 渲染缓冲区进行工作,也就是离屏渲染,这会给我们带来额外的性能损耗,如果这样的圆角操作达到一-定数量, 会触发 缓冲区的频繁合并和上下文的的频繁切换,性能的代价会宏观地表现在用户体验上一一掉帧。

    优化方案:使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角

    extension UIView {
        
        /// BezierPath 圆角设置
        func roundCorners(_ corners: UIRectCorner = .allCorners, radius: CGFloat) {
            let maskPath = UIBezierPath(
                roundedRect: bounds,
                byRoundingCorners: corners,
                cornerRadii: CGSize( radius, height: radius))
            
            let shape = CAShapeLayer()
            shape.path = maskPath.cgPath
            layer.mask = shape
        }
    }

    (2) shadow优化
    对于shadow,如果图层是个简单的几何图形或者圆角图形,我们可以通过设置shadowPath来优
    化性能,能大幅提高性能。示例如下:

    func setShadowLayer(_ views: [UIView], radius: CGFloat) {
        for view in views {
            let shadowPath = UIBezierPath(roundedRect: view.bounds, cornerRadius: radius)
            view.layer.masksToBounds = false
            view.layer.shadowColor = UIColor.black.cgColor
            view.layer.shadowOffset = CGSize( 3.0,height: 3.0)
            view.layer.shadowOpacity = 0.3
            view.layer.shadowPath = shadowPath.cgPath
        }
    }

    我们还可以通过设置shouldRasterize属性值为YES来强制开启离屏渲染。其实就是光栅化(Rasterization)。既然离屏渲染这么不好,为什么我们还要强制开启呢?当一个图像混合了多个图层,每次移动时,每一帧都要重新合成这些图层,十-分消耗性能。当我们开启光栅化后,会在首次产生一个位图缓存,当再次使用时候就会复用这个缓存。但是如果图层发生改变的时候就会重新产生位图缓存。所以这个功能一般不 能用于UITableViewCell中,cell的复 用反而降低了性能。最好用于图层较多的静态内容的图形。而且产生的位图缓存的大小是有限制的,一般是2.5个屏幕尺寸。在100ms之内不使用这个缓存,缓存也会被删除。所以我们要根据使用场景而定。

     

    (3)其他的一些优化建议

    ●当我们需要圆角效果时,可以使用- -张中间透明图片蒙上去

    ●使用ShadowPath指定layer阴影效果路径

    ●使用异步进行layer渲染(Facebook开 源的异步绘制框架AsyncDisplayKit)

    ●设置layer的opaque值为YES,减少复杂图层合成

    ●尽量使用不包含透明(alpha) 通道的图片资源

    ●尽量设置layer的大小值为整形值

    ●直接让美工把图片切成圆角进行显示,这是效率最高的一种方案

    ●很多情况下用户.上传图片进行显示,可以让服务端处理圆角

    ●使用代码手动生成圆角Image设置到要显示的View.上,利用UIBezierPath (CoreGraphics框架)画出来圆角图片

    (4) Core Animation工具检测离屏渲染

    对于离屏渲染的检测,苹果为我们提供了一个测试工具Core Animation。可以在Xcode- >Open Develeper Tools->Instruments中找到,如下图:

     
    page63image48021920
     Core Animation工具用来监测Core Animation性能,提供可见的FPS值,并且提供几个选项来测量渲染性能。如下图: 

    下面我们来说明每个选项的功能:

    Color Blended Layers:这个选项如果勾选,你能看到哪个layer是透明的,GPU正在做混合计算。显示红色的就是透明的,绿色就是不透明的。Color Hits Green and Misses Red:如果勾选这个选项,且当我们代码中有设置shouldRasterize为YES,那么红色代表没有复用离屏渲染的缓存,绿色则表示复用了缓存。我们当然希望能够复用。Color Copied Images:按照官方的说法,当图片的颜色格式GPU不支持的时候,Core Animation会拷贝一份数据让CPU进行转化。例如从网络.上下载了TIFF格式的图片,则需要CPU进行转化,这个区域会显示成蓝色。还有一种情况会触发Core Animation的copy方法,就是字体不对齐的时候。如下图:

    色,如果勾选这个选项则移除10ms的延迟。对某些情况需要这样,但是有可能影响正常帧数的测试。Color Misaligned Ilmages:勾选此项,如果图片需要缩放则标记为黄色,如果没有像素对齐则标记为紫色。像素对齐我们已经在上面有所介绍。Color Offscreen-Rendered Yellow:用来检测离屏渲染的,如果显示黄色,表示有离屏渲染。当然还要结合Color Hits Green and Misses Red来看,是否复用了缓存。Color OpenGL Fast Path Blue:这个选项对那些使用OpenGL的图层才有用,像是GL KView或者CAEAGLL ayer,如果不显示蓝色则表示使用了CPU渲染,绘制在了屏幕外,显示蓝色表示正常。Flash Updated Regions:当对图层重绘的时候回显示黄色,如果频繁发生则会影响性能。可以用增加缓存来增强性能。

    性能优化是一个持续的过程,未完待续。。。。。

  • 相关阅读:
    go基础系列:结构struct
    梯度下降法解决线性回归
    梯度下降法解决线性回归
    梯度下降法解决线性回归
    【 Linux 】单台服务器上并发TCP连接数(转)
    【 Linux 】单台服务器上并发TCP连接数(转)
    【 Linux 】单台服务器上并发TCP连接数(转)
    axios在vue项目中的一种封装方法
    快速排序算法(C#实现)
    你必须知道的261个Java语言问题
  • 原文地址:https://www.cnblogs.com/duzhaoquan/p/12421023.html
Copyright © 2020-2023  润新知