UITableView 性能优化

UITableView 是开发中经常会用到的控件,UITableView 中由一个个的 UITableViewCell 组成。对于一些简单的 UITableViewCell ,快速滑动时没什么问题。然而对于比较复杂的 UITableViewCell ,比如微博的 cell,cell 里面包含的内容非常多,有图片(图片数量不固定,不同数量对应的布局也不同),有文案(文案的长度不固定),还有很多小的控件,这种复杂的 cell 快速滑动起来时,可能会有卡顿的感觉。那么对于一些元素比较多的,布局比较复杂的 UITableViewCell,应该如何来优化呢?

fps概念

fps 全称 frame per second。可以理解为每秒钟的帧率,也即每秒钟屏幕刷新的次数。每秒钟帧数越多,所显示的动作就越流畅。iOS 设备每秒钟刷新的频率是60,通常情况下,iOS 应用fps保持在50-60之间不会感觉到卡顿,否则可能会感觉到卡顿。
Wiki 中 fps的定义如下:

Frame rate (expressed in frames per second or FPS) is the frequency (rate) at which consecutive images called frames are displayed in an animated display. The term applies equally to film and video cameras, computer graphics, and motion capture systems. Frame rate may also be called the frame frequency, and be expressed in hertz.

如何检测fps

既然 fps 可以检测应用是否卡顿,那么如何获得当前的 fps 呢?
方法一: fps的值可以通过 Instruments 检测出来。Instruments 中的 Core Animation Frames Per Second 表示的就是fps的值,调试时,操作app,fps 的值可以在 Instruments 中实时的显示出来。
方法二:使用 CADisplayLink 检测 fps
CADisplayLink 是一个和屏幕刷新频率相同的定时器。在应用中创建一个 CADisplayLink对象,把它添加到一个runloop中,并且提供target 和 selector。这样,每次屏幕刷新后,就会调用selector。默认情况下,CADisplayLink 的频率和屏幕刷新频率是一致的,也就是说 CADisplayLink 的 selector一秒钟被执行60次。这样,根据一秒钟 CADisplayLink 的selector的被执行了多少次,也就可以检测出屏幕一秒钟被刷新了多少次。
需要指出的是,使用 CADisplayLink 检测 fps 并不是完全准确的,只是一个参考值。
使用 CADisplayLink 检测 fps 的示例代码如下:

CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkValueAdd)];
    self.link = link;
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

- (void)linkValueAdd
{
    if(self.lastTime == 0){
        self.lastTime = self.link.timestamp;
    }
    self.count++;
    NSTimeInterval diff = self.link.timestamp - self.lastTime;
    if(diff > 1){
        self.lastTime = self.link.timestamp;
        self.count = 0;
    }
}

CPU 优化

当列表中的 UITableViewCell 过于复杂时,快速滑动会有卡顿的感觉。由上面fps的概念可知,当发生卡顿时,屏幕刷新的频率达不到60次/秒。其原因是,快速滑动时,CPU 需要不断的计算各个控件的位置,高度等,GPU 需要不断的进行渲染。当CPU或者GPU 由于负载太大,来不及计算、渲染时,就会丢帧,这也就是卡顿产生的原因。
由上可知,不论是CPU还是GPU哪个产生性能问题,都会造成app卡顿,因此,在对列表进行优化时,需要从CPU 和 GPU 两个方面来进行优化。
使用 Instruments 中的TimerProfile 可以看出代码中每个方法的执行时间,找出耗时的方法,并且尽量避免这些耗时的方法。
下面是项目中针对 UITableView 优化的一些建议:
(1)一些常用到的变量,能计算一次就计算一次,不要频繁的计算。举例来说,屏幕宽度,如果需要多次使用,尽管我们可以使用宏定义,但是宏定义只是简单的替换,实际上每次用到的时候还是要计算。如果在计算cell 高度的方法中需要多次用到屏幕宽度,那么可以定义变量,计算一次,之后直接使用该变量,减小cpu的计算量。
(2)尽量避免初始化控件。使用TimeProfile 时,发现初始化一个控件所花费的时间,要远远大于一行普通的代码。如:
UIButton *btn = [[UIButton alloc] init];
一个控件可能影响不大,但是当控件数量比较多,且方法调用频繁时,初始化控件会明显的影响到列表滑动性能。因此,能不使用新控件,就不使用新控件。
(3)一些动态计算控件size的方法能不用就不用。比如说 sizeToFit、sizeThatFits 方法,这些方法相对于一些普通的代码来说,所话费的时间是普通代码的几十倍。因此,在计算一些控件的size时,如果可以使用绝对的布局,或者说不需要严格的一个size,那么可以直接将控件大小写在代码里,而非到处使用 sizeToFit 方法,减小CPU 压力。
(4)使用TimeProfile 查看耗时的代码,能优化的尽量优化,不能优化的,看一下能否放到子线程,如果能放到子线程则让子线程去处理,不要阻塞主线程。

GPU优化

移动设备中,GPU负责渲染。当所需要渲染的区域过于复杂时,也会影响滑动的性能。

图层混合

当多个视图重叠在一起显示时,GPU 为了渲染出最终呈现的效果,会把多个视图混合在一起。如果视图结构过于复杂,混合的过程会消耗过多的GPU资源,引起性能问题。
为避免引起图层混合,有一些小的tip:
(1)控件尽量不要设置透明的背景
(2)初始化控件时,设置控件的背景颜色(实际上和第一条一致)
(3)图片尽量设置成无透明度的
(4)能用一个控件完成的布局,不要使用多个控件重叠的形式。既可以减轻cpu的计算,也能减少GPU的计算。

离屏渲染

在iOS系统中,CPU负责计算,GPU负责渲染。通常情况下,CPU只有一块缓存区用于渲染(当前屏幕的缓存区)。但是在一些特殊的操作时,比如设置圆角,设置阴影,GPU在当前的缓存区不能完成渲染,就会新开辟一块缓存区用于渲染,这就是离屏渲染。
离屏渲染对性能是有影响的,主要体现在两方面:
(1)首先开辟一块新的缓存区本身对性能就是有影响的;
(2)GPU需要在原缓存区和新缓存区之间不停的切换上下文,这样的操作对性能也是有影响的。

一些通用的优化

除了上面所说的针对CPU、GPU的优化外,对于UITableView,还有一些通用的优化点。
(1)复用 UITableViewCell ,这个可以有效的减小内存,也可以避免每次初始化子控件消耗大量的时间
(2)缓存 UITableViewCell 的高度,因此在 UITableView 滑动时,会频繁的使用到 UITableViewCell 的高度,因此可以缓存 UITableViewCell 的高度,对每一个cell只计算一次高度,减小 cpu 的计算量。
(3)缓存 UITableViewCell 中子控件的 frame。同第二条一样,由于计算高度,初始化控件是比较耗费时间的,因此如果控件的 frame 能只计算一次的话就只计算一次,减小 cpu 的计算量。这条和上一条其实都是以空间换时间的做法。
(4)网络上的图片异步加载,以及列表滑动过程中尽量不要加载图片。滑动过程中不加载图片这一点是需要和产品协商的,因为这一点毕竟是牺牲了用户体验。

总结

以上就是在实际工作项目中遇到 UITableView 卡顿时,所进行的一些优化措施。这些只是我使用的一些方法,并不是全部的优化方法。大家如果还有其他的优化方案,欢迎留言交流。

猜你喜欢

转载自blog.csdn.net/tugele/article/details/79520886
今日推荐