每日一问24——UiTableview性能调优

前言

UITableView是我们开发APP最常使用到的控件之一,使用它可以向用户展示大量的数据和信息,为了把这些数据更好的展示给用户,tableview的样式可能会变得非常复杂但美观。在我们制作出一个好看的tableview的同时,性能问题也逐渐走入了我们的视线。

为了让tableview的在美观的同时保持良好的性能,我们需要了解影响tableview性能的原因还有如何优化这些问题。
首先我们要知道,我们滑动时看到界面卡顿是由CPU和GPU开销共同决定的(参考每日一问02——渲染流程)

1.cell重用

一个tableview中往往会存在很多cell,如果每显示一个cell都要重新加载,那需要的开销将会是非常庞大的。苹果已经为我们提供了最基本的cell重用机制。

//将cell加载到内存
- (void)registerNib:(nullable UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
//使用内存中可重用的cell
- (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;

2.高度计算

当只有一种高度的情况下,我们可以直接指定cell的高度,这样是没有性能影响的。但很多时候会因为内容换行,图片等不确定因素导致行高不一致。这个时候就需要计算行高或者让系统计算行高。

>动态计算高度

iOS7后,系统给我们提供了estimatedRowHeight这个属性,让我们预估一个cell的高度由系统根据具体的约束计算cell高度。

self.tableView.estimatedRowHeight = 80;
>手动计算高度

很多时候我们会通过cell内控件的布局,约束,文字高度来计算这个cell的具体高度。我们会在代理方法heightForRowAtIndexPath中返回cell的具体高度。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    return [cell getHeight];
}

这样做虽然解决了动态高度cell的展示,但会因为每加载一个cell就会进行一次计算导致CPU计算量增加,如果cell中布局非常复杂,那这个消耗是很大的。

>缓存计算高度

对于计算过一次的高度来讲,我们不要再次计算,所以我们可以把计算好的高度进行缓存。保存再model里。当需要展示同一个cell时就可以直接使用上一次计算出来的高度。

3.渲染

渲染是由CPU和GPU共同完成的,在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃。就造成了我们看到的卡顿现象。而CPU消耗主要在各种计算上,图形图像的计算主要则是靠GPU完成的。所以我们需要让CPU和GPU利用率尽可能的均衡。

>减少GPU开销
  • 尽量使用不透明的视图进行布局
    当多个透明图像重叠时,GPU会进行计算,合成出一个颜色,这样的开销无疑是巨大的。所以在没有特殊需求的时候,我们可以设置layer的opaque来关闭合成,使用简单的拷贝图层而不考虑这个图层下面的东西。
  • 减少离屏渲染
    离屏渲染主要在两个地方开销较大:
1.创建新缓冲区,要想进行离屏渲染,首先要创建一个新的缓冲区。
2.上下文切换

我们要知道,CPU和GPU都可能造成离屏渲染。但我们可以让CPU和GPU负载均衡来保证渲染的性能。例如Core Graphics的所有API都会造成CPU的离屏渲染。

造成离屏渲染的主要方式
  • cornerRadius(圆角)
  • masks(遮罩)
  • shadows(阴影)
  • edge antialiasing(抗锯齿)
  • group opacity(不透明)

我们遇到的最多的就是添加圆角造成的离屏渲染,解决方案就是使用CPU来绘制圆角图片:参考(每日一问06——imageView的圆角优化)

>减少CPU开销
  • 图片预渲染
    当我们使用imageWithContentsOfFile加载一张图片时,图片只是被加载到了内存中并没有被渲染到UIimageView上,当UIImageView进行渲染时才会在主线程将这个图片解压成位图,这个解压的过程是一个非常耗时的CPU操作。

解决方案就是:在子线程预先对图片进行强制解压,生成可用的位图(每日一问04-加载图片对性能的影响)

4.异步

我们都知道UI操作是在主线程执行的,所以我们可以尽可能避免在主线程进行资源加载或对数据进行计算。把这些操作放在子线程中进行再显示在UI上。最常见的例子就是异步加载网络图片并显示。

5.减少多余的计算

很多时候tableview中会加载很多cell,而屏幕中只能展示其中那么几个。所以在滑动的过程中,如果我们只是在cellForRowAtIndexPath中对每一个cell都开线程进行图片或其他资源的加载,那么消耗将会非常大。

解决方案就是在快速滑动过程中,不执行异步加载,当滚动开始减速的时候才加载显示在当前屏幕上的cell。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    //如果没有取到,就初始化
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
    }
    cell.textLabel.text = @"Name";

    BOOL canLoad = !self.tableView.dragging && !_tableView.decelerating;
    if  (canLoad) {
        //开始loaddata,异步加载图片
        NSLog(@"开始加载图片");
    }
    return cell;
}


// 滚动停止时,触发该函数
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    //刷新tableview
   // [self.tableView reloadData];


 //在此刷新的是屏幕上显示的cell的内容
    NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows];
    //获取到的indexpath为屏幕上的cell的indexpath
    [self.tableView reloadRowsAtIndexPaths:visiblePaths withRowAnimation:UITableViewRowAnimationRight];

}

6.使用统一的cell

有的时候,我们界面上会存在许多样式不同,但布局方式很相似的cell。这时我们可以只编写一个cell样式,通过不同需求改变cell样式。这样做的好处有两点

1.减少代码量,减少Nib文件的数量,统一一个Nib文件定义Cell,容易修改、维护。
2.基于Cell的重用,真正运行时铺满屏幕所需的Cell数量大致是固定的,设为N个。所以如果如果只有一种Cell,那就是只有N个Cell的实例;但是如果有M种Cell,那么运行时最多可能会是“M x N = MN”个Cell的实例

在改变cell样式时,我们应尽量避免动态添加,移除subView,这样的操作会带来很多额外的计算。我们应该使用hidden方式直接隐藏和显示subView,这样要比重新创建subView性能高得多。

相关文章

提升UITableView性能-复杂页面的优化
优化UITableViewCell高度计算的那些事
iOS UITableView性能优化
iOS Tableview优化

你可能感兴趣的:(每日一问24——UiTableview性能调优)