UITableView最核心的思想就是UITableViewCell的重用机制。当TableView需要显示一个Cell时,会先从已创建的Cell中找一个可以重用的,然后展现到屏幕。一般情况下,可以被重用的Cell都滚到了屏幕区域外。如果慢慢地拖动TableView,就可以看到Cell不断地被重用(通过断点可以看到Cell的init或awakeFromNib没有被调用)。但是如果快速滚动,还是可能会看到Cell被创建。UITableView只会创建一屏幕(或一屏幕多一点)的UITableViewCell,其他都是从中取出来重用的。
则指定其ReuseIdentify,在delegate返回Cell的时候,调用:
[tableView dequeueReusableCellWithIdentifier:kCellID];
则需要先注册:
[tableView registerNib:[UINib nibWithNibName:kCellID bundle:nil] forCellReuseIdentifier:kCellID];
缓存基本上可以解决大部分性能问题。TableView需要知道Cell的高度,才能对Cell进行布局;需要知道所有的Cell的高度,才能知道TableView本身的高度,所以,每次调用reloadData,都需要计算所有Cell的高度。我们要尽量减小高度计算的复杂度。
UITableView最主要的两个回调方法是tableView:cellForRowAtIndexPath:
和tableView:heightForRowAtIndexPath:
。
UITableView的回调顺序是先多次调用tableView:heightForRowAtIndexPath:以确定contentSize及Cell的位置,然后才会调用tableView:cellForRowAtIndexPath:,从而来显示在当前屏幕的Cell。
所以 heightForRowAtIndexPath:是调用最频繁的方法。
思路是把赋值和计算布局分离。这样让tableView:cellForRowAtIndexPath:方法只负责赋值,tableView:heightForRowAtIndexPath:方法只负责计算高度。尽量不将Cell的实例放入 tableView:heightForRowAtIndexPath: 中。
在创建TableView的时候,直接设置其rowHeight属性。
实现代理方法,根据Cell的类型返回不同的高度:
比如 ACell 都是50;BCell都是80 之类的。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
由于需要动态计算高度,所以运算量必然会增大,但是还是存在优化的空间。常见的优化方式是,把cellHeight作为data的一个属性缓存起来,对于每一个data对应的每一个cell,就只需要计算一次高度。
当然,这样的方式,还是把运算量放到了TableView的代理方法内,其实也可以在创建ContentInfo本身的时候,就调用它的calcHeight方法,在代理方法里就可以可以直接返回info.cellHeight了。但也要结合实际情况进行取舍,因为有时候,有了数据源,但不一定需要展示TableView,提前计算高度反而会浪费时间。
比如每一个Cell都需要用到的UIImage,UIFont,NSDateFormatter或者任何在绘制时需要的对象,推荐使用类层级的初始化方法中执行分配,并将其存储为静态变量。也可以缓存整个View。
如果发现通过StoryBoard+xib+AutoLayout创建Cell时性能满足不了需求,可以考虑去掉AutoLayout。
如果不用AutoLayout还是有问题,可以考虑通过代码创建Cell的Views。
如果使用代码创建还是解决不了问题,那就只能靠自绘了,重载Cell的drawRect方法即可。
因为Cell上添加系统控件的时候,实质上系统都需要调用底层的接口进行绘制,当我们大量添加控件时,对资源的开销也会很大,所以我们可以索性直接绘制,提高效率。
如果在重写drawRect方法就不需要用GCD异步线程了,因为drawRect本来就是异步绘制的。对于图文混排的绘制,可以移步Google,研究下CoreText,这块内容太多了,不便展开。
如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定10行加载。
可以在UIScrollView的代理 scrollViewWillEndDragging中判断 行数与指定行的差距,若差距超过一定值,就不显示Cell。
这个在大量图片展示,网络加载的时候很管用,可以配合SDWebImage异步加载。
子View的层级越深,渲染到屏幕上所需要的计算量就越大。
对于不透明的View,设置opaque为YES,这样在绘制该View时,就不需要考虑被View覆盖的其他内容,避免GPU对Cell下面的内容也进行绘制。
给Cell中View加阴影会引起性能问题,如下面代码会导致滚动时有明显的卡顿:
view.layer.shadowColor = color.CGColor;
view.layer.shadowOffset = offset;
view.layer.shadowOpacity = 1;
view.layer.shadowRadius = radius;
可以尝试用 shadowPath创建阴影。
尽量显示“大小刚好合适”的图片资源。避免大量的图片缩放、颜色渐变等。
更多可以参考 http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/