前言
UITableView作为iOS开发中使用最频繁的控件之一,其性能优化也是常常需要面对的,尤其是当数据量偏大并且设备性能不足时。为了提高程序的稳定性和良好的用户体验,UITableView的优化很有必要。
影响性能的主要原因
影响性能的原因有很多种,以下列举最主要的几点:
1、 cellForRowAtIndexPath方法中处理了过多的业务。
2、 tableViewCell的SubView层级太复杂,做了大量的透明处理。
3、 cell的height动态变化时计算方法不对。
优化方案
关于影响性能的前两点比较好理解,此文档主要针对第三种情况做以处理。想要对tableView做性能优化,只能从tableView的数据源方法入手,而tableView的代理方法中,主要有两个方法为我们所用:cellForRow和heightForRow。下面我们针对这两个方法进行深入分析,并给出解决方案。
tableView:heightForRowAtIndexPath
TableView在每次reload data 时都需要所有cell的高度。也就是说如果有100行cell,那么每调用一次reload data ,就会调用100次heightForRowAtIndexPath方法获取每个cell的高度,而不是说只获取当前屏幕显示的cell的高度。 对于高度的计算,这里有个小细节需要注意,如果所有cell的高度都固定,那么就删除代理中的这个tableView:heightForRowAtIndexPath:方法。设置tableView的rowHeight属性。苹果的文档里也介绍了这样可以减少调用时间。 那么对于类似微博、微信朋友圈使用动态的高度,不定数量的文字,可能会有图片,数量和大小也不固定的情况下,传统的方法是为cell写个计算行高的类方法,传入那些动态的元素(文字,图片等),然后返回计算后的高度。在tableView:heightForRowAtIndexPath:中调用这个方法,填入需要的参数来计算cell高度。当然这没有什么问题,而且我们程序里也是这么做的。但是如果计算量很复杂,每次reload Data,光计算行高就花去了rowCount * 单行高的计算时间。假如有100行或更多时,程序里可能不定期的需要reload data 或者insertRow亦或者deleteRow,那么调用时间和内存消耗都很大。
解决办法:用“空间换时间”
用“空间换时间”简单说就是将计算行高的时间提前到从服务器返回数据的时候,计算完了行高一并写入数据库中。
2. tableView:cellForRowAtIndexPath:
在cellforRow回调的优化上,思路同上,也是通过预处理减少在这个回调中的计算时间。不过,主要针对图片的异步加载进行优化。
图片的异步加载无非就是在这个方法里发起异步请求,图片加载完后根据UIImageView的引用设置图片。在这里,使用懒加载的方式减少快速滑动时因为网络请求过于频繁与切换线程显示图片造成卡顿。还有个细节,从服务器拿回来的图片和最后显示的大小可能不一样。这就涉及到对图片的压缩。在显示时,控件加了约束,图片可能会适应控件大小,图片变形需要对图片做transform。每次压缩图片都要对图片乘以一个变换矩阵,如果图片很多,这个计算量是不容忽视的。
优化建议:
从网络拿回图片后,先根据需要显示的图片大小切成适合大小的图,每次只显示处理过大小的图片。当查看大图时再显示大图,如果服务器能直接返回预处理好的小图和图片的大小更好。
Ps:我们公司的程序里,文件服务器会有多种格式的图片大小,每次请求时会附带大小规格,但是,目前文件服务器支持的小图规格和UI上的控件大小还是不一致,还是会进行压缩。
图片压缩
对于图片的缩放一般有两种情况:等比例缩放和直接缩放至指定大小。
- ″等比例缩放
等比例缩放代码如下(给UIImage添加一个扩展方法)
// 等比缩放
- (UIImage *)hyb_cropEqualScaleImageToSize:(CGSize)size {
CGFloat scale = [UIScreen mainScreen].scale;
// 这一行至关重要
// 不要直接使用UIGraphicsBeginImageContext(size);方法
// 因为控件的frame与像素是有倍数关系的
// 比如@1x、@2x、@3x图,因此我们必须要指定scale,否则黄色去不了
// 因为在5以上,scale为2,6plus scale为3,所生成的图是要合苹果的
// 规格才能正常
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
CGSize aspectFitSize = CGSizeZero;
if (self.size.width != 0 && self.size.height != 0) {
CGFloat rateWidth = size.width / self.size.width;
CGFloat rateHeight = size.height / self.size.height;
CGFloat rate = MIN(rateHeight, rateWidth);
aspectFitSize = CGSizeMake(self.size.width * rate, self.size.height * rate);
}
[self drawInRect:CGRectMake(0, 0, aspectFitSize.width, aspectFitSize.height)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- 直接缩放至指定大小
// 非等比缩放,生成的图片可能会被拉伸
- (UIImage *)hyb_cropEqualScaleImageToSize:(CGSize)size {
CGFloat scale = [UIScreen mainScreen].scale;
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
[self drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
声明:本文部分内容参考于编程小翁和标哥博客。