影响 UITableView 滚动的流畅性的原因
1、在代理方法中做了过多的计算占用了 UI 线程的时间
2、Cell里的图片吃GPU(在tableView:cellForRowAtIndexPath:中)
3、Cell 中 view 的组织复杂
关于第一点,首先要明白 tableview 的代理(这里指 datasource 和 delegate 的那套方法,下同)方法的调用顺序,和时机。对于一般的应用会有如下顺序:
1、向代理要 number Of Rows。
2、对于每行向代理要 height For Row At Index Path。
3、向代理要 当前屏幕可见的 cell For Row At Index Path 。(实测显示4寸屏的手机会取 屏幕显示数量+2,3.5寸屏同4寸屏数量,虽然3.5寸屏可显示的cell 数量要小于 4寸屏!)
4、然后 cell 就显示出来了。
tableView:heightForRowAtIndexPath:
很多人都把优化的重点放到了 cell for row at indexpath 那个方法里了,在这里尽可能的少计算,但是却忽略了另一个很轻松就能提升加载时间的方法(尽可能的让 height For Row At Index Path这个方法的计算复杂度为 O(1)) :
1 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 2 // Table View 在每次 reload data 时都会要所有 cell 的高度!这就是说你有一百行 cell,就像代理要100次每个cell 的高度,而不是当前屏幕显示的cell 的数量的高度! 3 // 结论: 4 // 1、对于cell的高度不一定一样的:应该把cell的高度缓存起来。将计算行高的时间提前到,从服务器搂回数据的时候,初始化model的时候就马上计算行高,再缓存起来。例如可以缓存到FrameModel里 5 // 2、对于cell的高度都一样的:不要重载这个代理方法,直接在初始化tableView的地方赋值给rowHeight属性,同理,各分区头或各分区尾高度一样的话,应用sectionFooterHeight和sectionHeaderHeight,而不用代理方法。 6 // 3、如果只是刷新某row就可以解决问题的话,尽量用reloadRowsAtIndexPaths:withRowAnimation\reloadSections:withRowAnimation,而非用reloadData 另外同理,尽量用insertRowsAtIndexPaths:withRowAnimation:deleteRowsAtIndexPaths:withRowAnimation:等,代替reloadData。 7 }
tableView:cellForRowAtIndexPath:
说完了计算 cell 行高的优化,现在来谈 tableView:cellForRowAtIndexPath: 回调的优化。优化思路同上,也是通过预处理减少在这个回调中的计算时间。这个回调重点谈的是对图片异步加载的优化。
图片异步加载无非就是在这个方法里发起异步请求,图片加载完后根据 UIImageView 的引用设置图片。有经验的程序员可能会使用 懒加载 的方式减少快速滑动时因为网络请求过于频繁与切换线程显示图片造成的卡顿。这里还有个问题,拿回来的图片一定和最后显示的大小不一样,有时候偷懒,直接设置 image view 的 contentMode 属性要 image view 自己 压缩。这是一个很取巧的方法,但是对 table view 的滚动速度也会造成 不容忽视 的影响。对图片变形需要对图片做 transform ,每次压缩图片都要对图片乘以一个变换矩阵,如果你的图片很多,这个计算量是不同忽视的。
优化建议:
1、与后台和UI的同事沟通,在iOS端显示的要是尺寸小的缩略图,而非原图。
2、缩略图要不透明的png。(如果背景不是单一颜色的话,那就只好是透明背景的png,但这毕竟是极端少数的情况)
3、把cell以及cell里面的view(例如UIImageView)设置为不透明,把opaque的值设置YES(xib就是把勾勾上)
4、如果与后台和UI同事沟通无果,从网络搂回来图片都是很大的话,先根据需要显示的图片大小切成合适大小的图。
另外,有一种情况,后台不能返回圆角的,cell的图片需要做圆角,要自己画的。通常我们先会想到类似下面的方法:
1 view.layer.cornerRadius = 5; 2 view.layer.maskToBounds = YES;
如果你的圆角视图不多,cell 不复杂,的确用上面两句就好。但实际工作中,多数是大量的cell,则性能的损耗是非常大的。
优化建议:
1、如果能够只用 cornerRadius 解决问题,就不用优化。
2、如果必须设置 masksToBounds ,可以参考圆角视图的数量,如果数量较少(一页只有几个)也可以考虑不用优化。
3、UIImageView 的圆角通过直接截取图片实现,其它视图的圆角可以通过 Core Graphics 画出圆角矩形实现。
1 extension UIImage { 2 func kt_drawRectWithRoundedCorner(radius radius: CGFloat, _ sizetoFit: CGSize) -> UIImage { 3 let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: sizetoFit) 4 5 UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale) 6 CGContextAddPath(UIGraphicsGetCurrentContext(), 7 UIBezierPath(roundedRect: rect, byRoundingCorners: UIRectCorner.AllCorners, 8 cornerRadii: CGSize(width: radius, height: radius)).CGPath) 9 CGContextClip(UIGraphicsGetCurrentContext()) 10 11 self.drawInRect(rect) 12 CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke) 13 let output = UIGraphicsGetImageFromCurrentImageContext(); 14 UIGraphicsEndImageContext(); 15 16 return output 17 } 18 }
1 extension UIImageView { 2 /** 3 / !!!只有当 imageView 不为nil 时,调用此方法才有效果 4 5 :param: radius 圆角半径 6 */ 7 override func kt_addCorner(radius radius: CGFloat) { 8 self.image = self.image?.kt_drawRectWithRoundedCorner(radius: radius, self.bounds.size) 9 } 10 }