UITableViewCell自动高度计算优化总结
背景
- 一个主播聊天室,大量观众能发送大量的消息,赠送一些道具也会导致产生大量的道具消息。这里假设消息很多的情况:
每秒5条消息到来
。 - 消息使用NSAttributedString实现,
消息中包含不同大小的图片和文字
。 - 消息到来后,
自动滚动到最后一条消息
。 - 全局消息列表(存储最近500条消息,到达500条后直接删除最早的300条)
要求
- 不能占用大量CPU。
- 在大量消息到来的情况下,界面不能卡顿。
功能实现
自动计算高度
使用一个不错的开源库 UITableView-FDTemplateLayoutCell
这个开源库在数据较少时没有问题,但当数据(消息)不断增加时,会导致高度计算量大增。
高度计算占用CPU的原因
自动布局的高度计算速度慢。这个函数:systemLayoutSizeFittingSize
计算慢。
使用estimated效果不好
- (CGFloat)tableView:(UITableView * _Nonnull)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath * _Nonnull)indexPath
估计高度,会减少很多高度的计算量,每当Cell需要显示的时候才会计算高度。但大量消息的到来会导致Cell抖动,看起来很不舒服。
因为新增Cell是先按照估计高度显示,如果实际所需高度与估计高度不同,会在Cell显示后,再有个调整高度的过程(会有个简单的动画效果。这个效果如果在新增一条消息时很不多,但如果大量消息很短的间隔到来,就会导致Cell跳动起来)
使用缓存
不能使用IndexPath缓存:如果使用IndexPath缓存,则当消息到达500条,删除最早300条时,会导致所有Cell都需要重新计算高度。然而,剩余的那200条消息是已经计算过的。
由于消息一旦产生就不会变化,可将计算后的Cell高度再一次缓存到消息中。
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSInteger row = [indexPath row];
MessageEntity *msg = self.data[row];
if(msg.heightCache > 0){
//当缓存有缓存的高度时,直接返回对应的高度
return msg.heightCache;
}
CGFloat height = [tableView fd_heightForCellWithIdentifier:@"Cell" cacheByIndexPath:indexPath configuration:^(id obj) {
MessageTableViewCell *cell = obj;
// Fill Cell
}];
msg.heightCache = height;
return height;
}
或者,如果消息有一个唯一id,可以使用id缓存。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
Entity *entity = self.entities[indexPath.row];
return [tableView fd_heightForCellWithIdentifier:@"identifer" cacheByKey:entity.uid configuration:^(id cell) {
// configurations
}];
}
自动滚动
由于消息产生速度过快,比滚动到最后一行的速度还快。因此,增加个延迟。并过滤到延迟到来之前的滚动请求。