UITableView 是咱们开发中经常使用到的控件。其优化也是老生常谈的话题。笔者在这里抛砖引玉。html
IM模块的头像, 笔者的项目用UIButton。ios
早就据说iOS 设置圆角会形成性能上的开销。设置cornerRadius和masksToBounds 会发生离屏渲染。swift
但在iOS 9后,苹果对圆角问题进行了优化。UIImageView里png图片经过以上属性设置圆角不会触发离屏渲染(在iOS 12.1下亲测)。但UIButton设置图片和圆角,UILabel设置layer.backgroundColor 均会形成离屏渲染。数组
这方法会致使内存暴增,还会离屏渲染。并无优化,反而恶化了。bash
此方法用CPU渲染。CPU渲染能力不如GPU,但圆角这种轻量级渲染,CPU仍是能胜任的。重点是GPU离屏渲染须要上下文切换,严重时会形成卡顿。异步
此方法缺点,CPU以及内存 额外开销。oop
这里说的并非cell的复用,而是cell中部分view 的复用。布局
IM模块中,消息发送状态view,有三种状况,发送中圈圈,发送失败感叹号,发送成功没有发送状态view。性能
因为大多数消息都是发送成功的,因此有 发送状态view 的cell比较少,一个界面可能最多就一两个状态view。每一个cell都建立会浪费内存。
(固然平常项目,UIScrollView同样的view也能够相似思路优化)
思路:新建一个类ViewCache。两个数组,一个装着正在用的view,另外一个装着缓存中的view。
当cell设置model时,若是发送失败状态,cell没有statusView,就取缓存数组取,缓存数组空就新建一个,而且放到正在用的view数组中。当不用时,就放回缓存数组中。
- (void)setModel:(CellModel *)model {
switch (model.status) {
case 失败:
if (!self.statusView) {
self.statusView = [self.viewCache dequeueStatusView]
[self.contentView addSubView:self.statusView];
}
self.statusView.frame = model.layout.statusViewFrame;
break;
case 发送中:
// 差很少
break;
default:
if (self.statusView) {
[self.viewCache removeStatusView:self.statusView];
[self.statusView removeFromSuperview];
}
break;
}
}
复制代码
先来了解数据源、代理方法的调用时机。
网上有文章iOS开发-简单科普下UITableView和UICollectionView代理执行顺序说heightForRowAtIndexPath 在 cellForRowAtIndexPath前。笔者下载Demo来测试确实如此。
但笔者本身写了一份Demo,亲测并非。
因而在一篇文章tableView代理方法执行顺序中发现真相。
其实文档中也说清了,实现了预期高度,实际高度方法会被延迟到 cell将要显示时 调用。
咱们知道 实现代理方法后, rowHeight 会失效。因此笔者伪一下代码(固然无凭无证乱猜)
if(self.delegate && [self.delegate respondsToSelector:@selector(tableView:cellForRowAtIndexPath:)]) {
return [self.delegate tableView:self cellForRowAtIndexPath:indexPath];
} else {
return self.rowHeight;// 默认高度44
}
复制代码
因此咱们也就能节省两个方法(respondsToSelector和高度方法)的开销。(苹果有没有针对这部分作优化不得而知)
动态高度有两种方法,一种是利用AutoLayout,另外一种是直接算frame。
简单的说,设置预算高度和estimatedRowHeight = UITableViewAutomaticDimension,而后cell中最下面的控件设置底部约束,撑开cell。
这方法不用实现高度代理方法,滑动条会在滚动过程当中从新调整。
可是AutoLayout最终须要转成frame。这里就无可避免开销比直接算frame大。因此若是cell很复杂,不建议用AutoLayout。
缓存高度须要用如下两个方法,返回Auto Layout后内容高度。
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize NS_AVAILABLE_IOS(6_0);
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority verticalFittingPriority:(UILayoutPriority)verticalFittingPriority NS_AVAILABLE_IOS(8_0);
复制代码
具体实现
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CellModel * model = self.models[indexPath.row];
return model.cellHeight ?: UITableViewAutomaticDimension;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CellModel * model = self.models[indexPath.row];
TestCell * cell = [TestCell cellForTableView:tableView model:model];
//高度缓存
if (!model.cellHeight) {
CGFloat height = [cell systemLayoutSizeFittingSize:CGSizeMake(tableView.frame.size.width, 0) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel].height;
model.cellHeight = height;
}
return cell;
}
复制代码
在model中,设置和布局相关的属性,cellHeight懒加载。(笔者项目封装了一个CellLayout对象,包含每一个控件的frame以及cell高度)
- (CGFloat)cellHeight {
if (!_cellHeight) {
CGFloat iconH = 20;
CGFloat contentH = [self contentH];//算出来
_cellHeight = iconH + contentH + 10;
}
return _cellHeight;
}
复制代码
而后在代理方法中
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CellModel *model = self.models[indexPath.row];
return model.cellHeight;
}
复制代码
iOS开发之UITableview之多种Cell高度自适应实现方案的UI流畅度分析
笔者能力有限,除了以上的优化策略,其实UITableView还有不少能优化的地方。之后笔者若是有机会,会尝试往如下方向优化。