2019-03-31 UITableView的行高缓存优化以及原
2019-03-31 本文已影响0人
Daniel梁
我们先抛出一个问题
当我们cell的行高随网络的数据不同出现变化的时候,我们要怎么做去保证用户滚动的时候不会感觉到卡顿呢。
如下图,我们有不同的图片在不同的View中。
屏幕快照 2019-03-31 下午4.00.51.png我们可以通过TableView 中 DataSource的代理方法
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
每次model改变的时候,我们通过调用tableView的reloadData,该方法的底层会清除所有的cell ! 并重新计算section高度。
由于他会清理所有的cell再去创建,所以我们传数据的时候最好一次把单个模型封装成一个模型数组,一次把全部数据传值,这样就不会调用太多次reloadData
reloadData的源码
- (void)_reloadDataIfNeeded
{
if (_needsReload) {
[self reloadData];
}
}
- (void)reloadData
{
//当数据源更新后,需要将所有显示的UITableViewCell和未显示可复用的UITableViewCell全部从父视图移除,
//重新创建
[[_cachedCells allValues] makeObjectsPerformSelector:@selector(removeFromSuperview)];
[_reusableCells makeObjectsPerformSelector:@selector(removeFromSuperview)];
[_reusableCells removeAllObjects];
[_cachedCells removeAllObjects];
_selectedRow = nil;
_highlightedRow = nil;
// 重新计算 section 相关的高度值,并缓存起来
[self _updateSectionsCache];
[self _setContentSize];
_needsReload = NO;
}
更多UITableView源码 http://www.cocoachina.com/ios/20161129/18220.html
问题又来了,我们调用了reloadData后,那么rowHeight的代理方法会怎么调用呢
IOS7
- 在ios7及以前,我们可以通过设置tableView的rowHeight属性或者实现dataSource的rowHeight代理方法去设置行高,苹果建议两者设置其中一者即可,想一下也是,两个都设置了其中一个会失效。
IOS8
- 在ios8以后,tableView中增加了新的属性tableView.estimatedRowHeight,及预估高度。苹果的初衷是他每个cell的高度会随数据源变化而变化,增加一个预估高度就可以预估出需要的高度,用户滚到下一个cell的时候再去计算其高度
可以看到ios8以后,苹果是推荐我们用预估行高去优化的,不然他也不会推出该属性
下面我们来看一下ios7和ios8的在页面滚动的时候调用rowHeight代理方法的次数
屏幕快照 2019-03-31 下午6.20.32.png
这图是来http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/
TemplateLayoutCell的开发团队,文章讲的不错可以去看看
我们抛开ios7不说,我们看看ios8后的事情,可以看到开启估算后计算的次数比关闭估算少很多次,关闭估算不仅在一开始就计算好所有cell的高度,而且在滚动的时候又重新去计算一次。。。开启估算滚动的时候会去计算高度
测试
下面来看看我在项目里面测试的数据
首先我cell的数据数量是16个左右,我现在要测试开关estimate调用了几次rowHeight
打开估算estimatedRowHeight = 400
屏幕快照 2019-03-31 下午6.28.23.png可以看到界面有三个cell ,每个cell调用了三次计算高度,一共九次
继续滑动的时候会再去计算其他cell的rowHeight,也是一个cell调用三次rowHeight。
关闭估算
屏幕快照 2019-03-31 下午4.12.42.png把16个数据的高度算出来了,而且滚动的时候会继续去计算,性能很糟糕.
到这里,我们可以想出对应的优化思路了,既然他每个cell都会去重新计算,课室cell的model是不变的,16个数据,我们缓存他16个高度就可以了,这样就不会重复去计算高度了。
模型中增加缓存高度的属性
//缓存行高
lazy var rowHeight : CGFloat = {
print("计算了rowHeight")
//类对象 引用类型
let cell = StatusCell.init(style: .default, reuseIdentifier: StatusCellID)
return cell.RowHeight(statusVM: self)
}()
rowHeight调用缓存高度
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return statuslistviewModel.StatusList[indexPath.row].rowHeight
}
计算rowHeight的方法调用了16次,往回滚的时候该方法没有被调用
说明使用了缓存,没有重新去计算
屏幕快照 2019-03-31 下午6.35.02.png