UITableView的优化

最近看到有些面试题中会问到TableView的优化,特定花了几天时间研究了一下各种优化技巧,主要就分为几个主要方向:

  • 从重用Cell的方面去优化
  • 从图层属性,圆角/阴影等方面去优化
  • 从UIView的绘制方面去优化
  • 从预计算和缓存高度,按需加载的方面去优化

从一个初学者写的卡到爆的TableView例子说起(引用自这里)

  • (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    ContacterTableCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ContacterTableCell"];
    if (!cell) {
    cell = (ContacterTableCell *)[[[NSBundle mainBundle] loadNibNamed:@"ContacterTableCell" owner:self options:nil] lastObject];
    }
    NSDictionary *dict = self.dataList[indexPath.row];
    [cell setContentInfo:dict];
    return cell;
    }
如果没有在xib中设置重用的标识,上面的cell就不会发生重用,只要TableView发生滚动,cellForRowAtIndexPath就会被调用,每一次cellForRowAtIndexPath被调用都会从NSBundle中获取View的示例再赋给cell。众所周知从NSBundle中读取数据是非常的慢的,所以这样写出的代码必定会卡到爆(我当初刚学iOS的时候就是这样写的(-_-))。

## 既然卡到爆就重用啰
首先注册cell

    table = UITableView(frame: self.view.bounds, style: UITableViewStyle.Plain)
    table?.registerClass(UITableViewCell.self, forCellReuseIdentifier: "mainViewControllerCellId")

然后重用

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("mainViewControllerCellId")
    // config cell data
    cell?.textLabel?.text = titles[indexPath.row]
    cell?.selectionStyle = .None
    return cell!
}
可以看到,使用了基本的重用机制后,整个TableView就变得流畅起来了,而且按照这种做法,cell的生产和使用就分开了,cellForRowAtIndexPath就只专注于设置cell的数据,不关心cell的初始化。一般的TableView界面采用这种写法都能获得不错的性能,而且有一定经验的iOS老手都是这样写的。

## 万一Cell中有圆角蒙版阴影透明这些图层属性呢
在UIView中,透明效果的渲染,圆角的渲染,蒙版的渲染,阴影的渲染会消耗很大部分的性能。在静态的View之中,这部分只消耗一次,不会造成太大的影响,但是在想UITableViewCell这些重用的动态的View中,每一次的重用都会消耗一次,所以总的性能消耗会很大。当然,要想在cell中使用这些属性,还是有优化的技巧来减少性能的消耗的。

#### 首先,尽量减少透明View。
UIView有一个属性```opaque ```,当不需要透明时可以将其设置为true,这样可以减少性能消耗。

#### 其次,减少离屏渲染
通常图层的以下属性将会触发离屏渲染:
* 阴影(UIView.layer.shadowOffset/shadowRadius/...)
* 圆角(当 UIView.layer.cornerRadius 和 UIView.layer.maskToBounds 一起使用时)
* 图层蒙板

对于以上这些,要减少离屏渲染比较好的方法是使用shadowPath来设置阴影,使用裁剪过的图片代替圆角而不是使用cornerRadius,图层模板能不用就不用。

#### 最后,合理使用光栅化
当然,除了第二点减少离屏渲染外,还有一点就是要合理使用光栅化。使用光栅化能够将能够有效提高性能,[把layer的shouldRasterize设为YES后,CALayer会被光栅化为bitmap,layer的阴影等效果也会被保存到bitmap中作为缓存。在使用了shadow或cornerRadius等效果时,缓存使性能得到提升](http://zhijiang.me/2015/08/03/%E5%BD%B1%E5%93%8D%E5%9B%BE%E5%BD%A2%E6%80%A7%E8%83%BD%E7%9A%84%E5%9B%A0%E7%B4%A0%E5%92%8C%E4%BD%BF%E7%94%A8Instrument%E8%BF%9B%E8%A1%8C%E6%A3%80%E6%B5%8B/)。

但是,使用光栅化要注意几点:

- 更新已经光栅化的CALayer会造成离屏渲染 (这最重要)
- 被光栅化的bitmap如果超过100ms没有被使用则会被移除
- 系统限制缓存的大小为2.5 x screen size

对于经常改动的,不易采用光栅化,否则会增加离屏渲染,增加性能消耗。对于相对静态的,建议采用光栅化。特别是界面比较复杂,动画比较复杂的,都建议使用光栅化。

## 糟糕我遇到了很复杂的Cell
由于绘制渲染复杂View的过程是非常耗时的,有时真的不排除遇到很复杂CellView的情况。由于本人对Core Graphics不太熟练,而且最近有点小忙,所以就不提供代码,只提供一些优化思路。
核心优化就两点:

1. 使用Core Graphics进行重绘,更进一步就是将所有subViews绘制成一张图片,通过消除View树的层级来提高效率。通俗来说就是所谓的扁平化。。。
通过重绘来压缩View树层级,能大大提高View的绘制效率,特别对于一些复杂界面效果特别明显。当然缺点就是使用重绘比较复杂,开发一些相对简单的界面可能会影响开发效率。

2.异步绘制渲染。
这个就是将View的绘制渲染放到后台线程中,完成后再通知主线程更新UI。这个思路跟Facebook的一个非常出名的开源框架[AsyncDisplayKit](https://github.com/facebook/AsyncDisplayKit)类似。

## 我想我的TableView更动人
如何更进一步优化TableView的体验?答案就是就两点:
- cell高度缓存和预计算
- 按需加载

对于第一点cell高度缓存和预计算,简单来说就是计算每一个cell的高度并缓存起来,避免重复计算。这里有个更合理的方法是[在用户不滑动TableView时,通过监听runloop来启动后台cell高度计算和缓存](http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/)。这样做的好处时将用户交互和耗时计算任务进行合理的分时,既不影响用户交互,也不浪费CPU计算性能。

对于第二点,用户滚动TableView的时候,希望能尽快看到当前屏幕显示的内容,而不关心其他还没有滚动到当前屏幕的内容。所以,一个比较有效的优化思路是优先绘制加载当前的内容,暂时忽略其他内容。根据这个思路,可以使用UIScrollView的协议方法,监听滚动的offset,然后当用户暂停滚动时的屏幕,就是需要优先加载的屏幕内容,这时候可以再去加载当前内容,而不是一开始按照滚动顺序全部加载。这样的话就不用特地设置一个网络请求的队列来管理网络请求,而且发出去了的请求也是cancel不了的。


## 总结
感觉对TableView的优化有了点思路,但是优化方法太多以至于还没消化过来,但是大的优化方向也是很清晰的:

- 能重用就重用
- 尽量减少view层级
- 使用异步绘制
- 使用缓存
- 按需加载
- 对于某些layer属性尽量采用替代方案

总之,这几天对这个专题的研究,收获很多,特定分享出来,写的不好,还望指教。

## 参考

[优化UITableViewCell高度计算的那些事](http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/)

[[iOS 程序性能优化](http://www.samirchen.com/ios-performance-optimization)](http://www.samirchen.com/ios-performance-optimization/)

[UITableView优化技巧](http://longxdragon.github.io/2015/05/26/UITableView%E4%BC%98%E5%8C%96%E6%8A%80%E5%B7%A7/)

[如何加强 iOS 里的列表滚动时的顺畅感?](http://www.zhihu.com/question/20382396)

[影响图形性能的因素和使用Instrument进行检测](http://zhijiang.me/2015/08/03/%E5%BD%B1%E5%93%8D%E5%9B%BE%E5%BD%A2%E6%80%A7%E8%83%BD%E7%9A%84%E5%9B%A0%E7%B4%A0%E5%92%8C%E4%BD%BF%E7%94%A8Instrument%E8%BF%9B%E8%A1%8C%E6%A3%80%E6%B5%8B/)

推荐阅读更多精彩内容