UITableView的优化技巧

来源:互联网 发布:淘宝产品复制 编辑:程序博客网 时间:2024/05/08 05:52

作者:Love@YR
链接:http://blog.csdn.net/jingqiu880905/article/details/51920117
请尊重原创,谢谢!

UITableView的优化是个难点也是个痛点,下面列出各路大神总结的优化技巧。

首先,基础篇:

cell绘制方面cellForRowAtIndexPath:

  • cell重用 reuseIdentifier
    这个可以看下上一篇文章,讲解了不同情况下alloc了多少个cell就比较清楚了。不用reuse你内存受不了。
    (也应该在section header和footer中使用reuseIdentifier)

  • 统一设计cell
    cell的Prototype最好高度抽象统一,越少越好,因为不同的Prototype有其对应的cell重用池,创建的cell种类越多,重用效率也就越低。所以能用一种就用一种。

  • 减少视图层级关系,减少subview的数量
    尽量少用addView给cell动态添加view,可以初始化时就添加,然后通过hide来控制是否显示。

  • 尽量使用opaque
    尽量使cell的contentView所有的subview都设置opaque,包括Cell自身。尽量少用或不用透明图层。

  • 绘制方法中尽量不要处理过多业务逻辑

高度计算方面heightForRowAtIndexPath:

  • row 的高度都一定的情况
    删除代理中的heightForRowAtIndexPath: 方法,设置 tableView的rowHeight属性,sectionHeaderHeight footer也同样的道理。

其他:

  • cell数据资源缓存
    一般大家也都会直接用个dictionary或者数组或者model来存下需要显示的数据源,不大可能会到用的时候再去读plist啊,拿数据库里的之类。没啥好说的。

  • UI的尺寸和行高缓存,用 “空间换时间”
    正常情况计算行高放在heightForRowAtIndexPath,但如果这个计算很复杂,时间不固定,有时短有时长,你再一reloaddata,或者insertrow,deleterow那就很烦了。
    所以将计算行高的时间(当然连同各UI的尺寸计算,你只有算出来所有子view高宽度才能确定cell高度)提前到从服务器拿到数据的时候就开始,计算完了将cell高度,各行各ui的frame一并写回数据源预处理。
    一次计算一劳永逸,避免在heightForRowAtIndexPath和cellForRowAtIndexPath方法里每次滚到这一行都去计算。

  • 图片预处理
    显示之前提前处理。比如缩放:
    如果你是在画cell的时候直接设置imageview的 contentMode 属性让 imageview自己缩放,缩放需要对图片做transform ,要对图片乘以一个变换矩阵,计算量很大,很耗性能,更何况你每次滚到那行都要缩放。
    所以可以在刚拿到图片就先缩放生成新图,当然最好是服务端能返回相对应大小的图片,这样就不用缩放了。而如果要看大图,最好是点击查看大图的时候再拿大图去渲染。设计时不宜把大图直接显示在列表里。

  • 图片异步加载
    图片下载放到后台,异步加载。
    不过如果每个循环对象都异步加载,很多个线程也会影响主线程的性能,所以好的方法就是按需加载,而不是全部在后台下载

  • 图片按需加载
    没有在滑动且加速度为0(即已经停止下来时)才去加载图片(不然你滑那么快我每个图都去请求网络下载然后显示,每个下载又是一个子线程,我线程各种切换,然后你刷地又滑回来。。。),且只去画当前可视区域的图片,或者再加上当前可视前后指定的几行。
    下载过的不再下载,正在下载的不去下载。
    (按需加载可参考上篇文章,苹果官方例子lazyloadImage的解析)

  • 图片资源尽量用png,因为iOS本身对png进行了很多优化

  • 关于reloaddata
    heightForRowAtIndexPath 在reload data时会执行所有cell高度全部刷新,而不是当前屏幕显示的cell数量。尽量不用reload data而用insertrow deleterow

  • 关于重复代码
    heightForRowAtIndexPath和cellForRowAtIndexPath中尽量不要有重复代码

  • 尽量少用xib storyboard创建cell,他们需要系统自动转码,也不能自定义cell绘制

其次,关于cell上图片圆角

  • cornerRadius方式
cell.myImageView.layer.cornerRadius = 8.0;  cell.myImageView.layer.masksToBounds = YES;//或者cell.myImageView.clipsToBounds = YES

这种方式会触发离屏渲染。iOS9之后png图片不会了。

  • 设置masklayer
CAShapLayer *layer = [CAShapLayer layer];UIBezierPath *bzpath = [UIBezierPath bezierPathWithOvalInRect:imageview.bounds] ;layer.path = bzpath.CGPath;cell.myImageView.layer.mask = layer

一次mask发生两次离屏渲染和一次主屏渲染,比上面的方法效率还低。(主要是上下文切换耗性能)
但是可不局限于圆角,由 mask 控制边角显示为什么形状。

  • 光栅化,也叫位图化
cell.myImageView.layer.cornerRadius = 8.0; cell.myImageView.layer.masksToBounds = YES;cell.myImageView.layer.shouldRasterize = YES;cell.myImageView.layer.rasterizationScale = [UIScreen mainScreen].scale;//UIImageView不加这句会产生一点模糊

设置光栅化,可以使离屏渲染的结果缓存到内存中存为位图,使用的时候直接使用缓存,节省了一直离屏渲染损耗的性能。但是如果layer及sublayers常常改变的话,它就会一直不停的渲染及删除缓存重新创建缓存,此情况下建议不要使用光栅化,比较损耗性能

  • 对图片进行切角

imageview的drawRect或者setImage里

UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);[[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:self.bounds.size.height/2] addClip];[imageFromServer drawInRect:self.bounds];circleImage = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();[super setImage:circleImage]

切角的操作是在CPU内完成的,而后我们只需要取到处理完成的bitmap(可为UIImage对象)交给GPU显示于屏幕即可。
无离屏渲染,把 GPU的压力转给 CPU,适用于 CPU 压力不大的情况,CPU和内存消耗增大。

  • mask图和原图合成
 UIImage *roundedImage = [self imageByComposingImage:image                                         withMaskImage:[UIImage imageNamed:@"mask.png"]];  cell.myImageView.image = roundedImage;- (UIImage *)imageByComposingImage:(UIImage *)image withMaskImage:(UIImage *)maskImage {    CGImageRef maskImageRef = maskImage.CGImage;     CGImageRef maskRef = CGImageMaskCreate(CGImageGetWidth(maskImageRef),                                         CGImageGetHeight(maskImageRef),                                         CGImageGetBitsPerComponent(maskImageRef),                                         CGImageGetBitsPerPixel(maskImageRef),                                         CGImageGetBytesPerRow(maskImageRef),                                         CGImageGetDataProvider(maskImageRef), NULL, false);  CGImageRef newImageRef = CGImageCreateWithMask(image.CGImage, maskRef);  CGImageRelease(maskRef);  UIImage *newImage = [UIImage imageWithCGImage:newImageRef];  CGImageRelease(newImageRef);  return newImage;}

不局限圆角,但效率比较低。

  • 直接覆盖一张中间为圆形透明的图片
    直接[imageview addsubview:中间透明的图片]
    然后再[imageview setImage:原图]

这种方法就是多加了一张透明的图片,GPU计算多层的混合渲染blending也是会消耗一点性能的

最后,关于Instrument来调试tableView性能

  • Core Animation
    debug options选择:
    Color Hits Green and Misses Red:
    光栅化对应的渲染结果会被缓存。如果图层是绿色,就表示这些缓存被复用;如果是红色就表示缓存会被重复创建,这就表示该处存在性能问题了。

    Color Offscreen-Rendered Yellow开启后会把那些需要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。

    Color misaligned images:
    洋红色代表像素没对齐???
    黄色代表图片有缩放
    解决方法是设置各view 的 frame 时用整数不用小数??
    和最好刚拿到图时就缩放生成新图而不是显示时再缩放

  • GPU Driver
    Renderer Utilization
    如果这个值超过了~50%,就意味着你的动画可能对帧率有所限制,很可能因为离屏渲染或者是重绘导致的过度混合

    Tiler Utilization
    如果这个值超过了~50%,就意味着你的动画可能限制于几何结构方面,也就是在屏幕上有太多的图层占用了。

    帧率Core animation frames per seconds 越接近60滑动越顺畅。

  • time profiler
    Call Tree那边设置全选,去查看哪句代码走的时间比较长

参考:http://tutuge.me/2015/02/19/提升UITableView性能-复杂页面的优化/
http://blog.csdn.net/hanmingsa/article/details/51648199

0 0
原创粉丝点击