iOS开发UITableView性能优化总结

UITableView是iOS开发中最常用的控件,UITableView性能优化也是老生常谈了,大致总结如下,以供参考

1.    把赋值和计算布局分离

    UITableView最核心的思想就是UlITableViewCell的重用机制。简单的理解就是: UlTableView只 会创建一屏幕(或一屏幕多一点)的UITableViewCell,其他都是从中取出来重用的。每当Cell滑出屏幕时,就会放入到一个集合(或数组)中(这里就相当于一个重用池),当要显示某一位置的Cell时,会先去集合(或数组)中取,如果有,就直接拿来显示;如果没有,才会创建。这样做的好处可想而知,极大的减少了内存的开销。

       知道UITableViewCell的重用原理后,我们来看看UITableView的回调方法。UITableView最 主要的两个回调方法是tableView:cllForRowAtIndexPath:和tableView:heightForRowAtIndexPath:。理想上我们是会认为UITableView会先调用前者,再调用后者,因为这和我们创建控件的思路是一样的,先创建它,再设置它的布局。但实际上却并非如此,我们都知道,UITableView是 继承自UIScrollView的,需要先确定它的contentSize及每个CelI的位置,然后才会把重用的Cell放置到对应的位置。所以事实上,UITableView的回调顺序 是先多次调用tableView:heightForRowAtIndexPath:以确定contentSize及Cell的位置,然后才会调用tableView:cellForRowAtIndexPath:,从而来显示在当前屏幕的Cell。举个例子来说:如果现在要显示100个Cell,当前屏幕显示5个。那么刷新(reload) UITableView时,UITableView会先调 用100次tableView:heightForRowAtIndexPath:方法,然后调用5次,tableView:cellForRowAtIndexPath:方法;滚动屏幕时,每当Cell滚入 屏幕,都会调用- -次tableView:heightForRowAtIndexPath:、tableView:cellForRowAtIndexPath:方 法。 这样写,在Cell赋值内容的时候,会根据内容设置布局,当然也就可以知道CelI的高度,想想如果1000行,那就会调用1000+页面Cell个数次tableView:(UITableView *)tableViewcellForRowAtIndexPath:(NSIndexPath *)indexPath方法,而我们对CelI的处理操作,都是在这个方法里的!什么赋值、布局等等。开销自然很大,这种方案Pass。。。改进代码。

思路是把赋值和计算布局分离。这样让tableView:cellForRowAtIndexPath:方法只 负责赋值,tableView:heightForRowAtIndexPath:方法只负责计算高度。注意:两个方法尽可能的各司其职,不要重叠代码!两者都需要尽可能的简单易算。Run一下,会发现UITableView滚动流畅 了很基于.上面的实现思路,我们可以在获得数据后,直接先根据数据源计算出对应的布局,并缓存到数据源中,这样在tableView:heightForRowAtIndexPath:方 法中就直接返回高度,而不需要每次都计算了。再一个就是我们经常在注意cellForRowAtIndexPath:中为每一.个cel绑定数据,实际上在调用.cellForRowAtIndexPath:的时候cell还没有 被显示出来。

(1)willDisplayCell

为了提高效率我们应该把数据绑定的操作放在cell显示出来后再执行,可以在tableView: willDisplayCell: forRowAtIndexPath: (以后简称willDisplayCell)方法中绑定数据。注意illDisplayCell在cell在tableview展示之前就会调用,此时cell实例已经生成,所以不能更改cell的结构,只能是改动cell 上的UI的一些属性(例如label的内容等) 。

(2)定义高度
1>新建一. 个继承自UITableViewCel的类
2>重写initWithStyle:reuseldentifier:方法
3>添加所有需要显示的子控件(不需要设置子控件的数据和frame,子控件要添加到contentView中)
4>进行子控件一次性的属性设 置(有些属性只需要设置一次, 比如字体\固定的图片)
5>提供2个模型
数据模型:存放文字数据\图片数据.
frame模型:存放数据模型\所有子控件的frame\cell的高度
6>cell拥有一个frame模型(不要 直接拥有数据模型)
7>重写frame模型属性的setter方法:在这个方法中设置子控件的显示数据和frame
(3)自定义高度原理
A手动计算
1>由于heightForRow比kcellForRow方法先调用,创建frame模型包含微博模型,重写微博模型赋
值set方法,提前计算cell子控件的frame并保存,heightForRow方 法中取出frame模型中保存的高
度,实现自定义高度cll
2>设置最大尺寸、文本属性,根据文本内容计算正文内容展示尺寸
3> cellForRow中创建自定义cell包含frame属性,重写frame属性set方法创建cell子控件并赋值
frame模型保存的子控件尺寸
B.自动计算
1>首先设置行高使用autolayout自动计算并预估高度
2>在stroboard中对cel内容进行自动布局,注意设置图片距离底部约束,cellForRow中 创建
storyboard中对应标记的自定义cell
3>由于正文内容的不确定性,设置label多行,拖线图片高度约束,根据图片有无,设置代码设
置高度约束

2 自定义Cell的绘制

       上面的改进方法并不是最佳方案,但基本能满足简单的界面!记得开头我的任务吗?像朋友圈那样的图文混排,这种方案还是扛不住的!我们需要进入更深层次的探究:自定义Cell的绘制。我们在Cell.上添加系统控件的时候,实质上系统都需要调用底层的接口进行绘制,当我们大量添加控件时,对资源的开销也会很大,所以我们可以索性直接绘制,提高效率。首先需要给自定义的Cell添加draw方法,(当 然也可以重写drawRect)然后在方法体中实现:

(1) TableView渲染

为了保证TableView的流畅,当快速滑动的时候,cell必须被快速的渲 染出来。所以ell渲染的速度必须快。如何提高cel的渲染速度呢?当有图像时,预渲染图像,在bitmap context先将其画一 -遍, 导出成Ullmage对象, 然后再绘制到屏幕,这会大大提高渲染速度。具体内容可以自行查找“利用预渲染加速显示iOS图像”相关资料(https://blog.csdn.net/qiaoxinde/article/details/50766844)。渲染最好时的操作之一就是混 合(blending)了,所以我们不要使用透明背景,将Cel的opaque 值设为Yes, 背景色不要使用clearColor, 尽量不要使用阴影渐变等由于混合操作是使用GPU来执行,我们可以用CPU来渲染,这样混合操作就不再执行。可以在UIView的drawRect方法中自定义绘制。

(2)减少视图的数目
我们在cell.上添加系统控件的时候,实际上系统都会调用底层的接口进行绘制,大量添加控件

时,会消耗很大的资源并且也会影响渲染的性能。当使用默认的UITableViewCell并且在它的ContentView.上面添加控件时会相当消耗性能。所以目前最佳的方法还是继承UITableViewCell,并重写drawRect方法。

(3)减少多余的绘制操作

在实现drawRect方法的时候,它的参数rect就是我们需要绘制的区域,在rect范围之 外的区域我们不需要进行绘制,否则会消耗相当大的资源。

(4)不要给cell动态添加subView

在初始化cell的时候就将所有需要展示的添加完毕,然后根据需要来设置hide属性显示和隐藏。

(5)异步化UI,不要阻塞主线程

我们时常会看到这样一个现象,就是加载时整个页面卡住不动,怎么点都没用,仿佛死机了一般。原因是主线程被阻塞了。所以对于网路数据的请求或者图片的加载,我们可以开启多线程,将耗时操作放到子线程中进行,异步化操作。这个或许每个iOS开发者都知道的知识,不必多讲

3 滑动时按需加载对应的内容

如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定3行加载。

-(void)scrollViewWillEndDragging:(UIScrollView *)scrollViewwithVelocity:(CGPoint)v elocitytargetContentOffset:(inoutCGPoint *)targetContentOffset{

NSIndexPath *ip=[selfindexPathForRowAtPoint:CGPointMake(0,targetContentOffset- >y)];

NSIndexPath *cip=[[selfindexPathsForVisibleRows]firstObject]; NSIntegerskipCount=8;
if(labs(cip.row-ip.row)>skipCount){

NSArray *temp=[selfindexPathsForRowsInRect:CGRectMake(0,targetContentOffse t->y,self.width,self.height)];

NSMutableArray *arr=[NSMutableArrayarrayWithArray:temp]; if(velocity.y<0){

:0]];

:0]];

:0]];
}

4 离屏渲染问题

1.离屏渲染的问题的造成,下面的情况或操作会弓|发离屏渲染:

●为图层设置遮罩(layer.mask)

●将图层的layer.masks' ToBounds / view.clips ToBounds属性设置为true

●将图层layer.allowsGroupOpacity属性设置为YES和layer.opacity小于1.0

●为图层设置阴影(layer.shadow*) 。

●为图层设置layer. shouldRasterize=true

●具有layer.cornerRadius, layer.edgeAntialiasingMask, layer.allowsEdgeAntialiasing的图层

●文本(任何种类,包括UIL abel, CATextl _ayer, Core Text等)。

●使用CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现

2离屏渲染优化方案

官方对离屏渲染产生性能问题也进行了优化: 

ios 9.0之前UlimageView跟UlButton设置圆角都会触发离屏渲染。

iOs 9.0之后UIButton设置圆角会触发离屏渲染,而UllmageView里png图片 设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。

(1)圆角优化

在APP开发中,圆角图片还是经常出现的。如果一个界面中只有少量圆角图片或许对性能没有非常大的影响,但是当圆角图片比较多的时候就会APP性能产生明显的影响。我们设置圆角一般通过如下方式:

imageView.layer.cornerRadius=CGFloat(10);

imageView.layer.masks ToBounds=YES;

这样处理的渲染机制是GPU在当前屏幕缓冲区外新开辟一个 渲染缓冲区进行工作,也就是离屏渲染,这会给我们带来额外的性能损耗,如果这样的圆角操作达到一-定数量, 会触发 缓冲区的频繁合并和上下文的的频繁切换,性能的代价会宏观地表现在用户体验上一一掉帧。

优化方案:使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角

extension UIView {
    
    /// BezierPath 圆角设置
    func roundCorners(_ corners: UIRectCorner = .allCorners, radius: CGFloat) {
        let maskPath = UIBezierPath(
            roundedRect: bounds,
            byRoundingCorners: corners,
            cornerRadii: CGSize(width: radius, height: radius))
        
        let shape = CAShapeLayer()
        shape.path = maskPath.cgPath
        layer.mask = shape
    }
}

(2) shadow优化
对于shadow,如果图层是个简单的几何图形或者圆角图形,我们可以通过设置shadowPath来优
化性能,能大幅提高性能。示例如下:

func setShadowLayer(_ views: [UIView], radius: CGFloat) {
    for view in views {
        let shadowPath = UIBezierPath(roundedRect: view.bounds, cornerRadius: radius)
        view.layer.masksToBounds = false
        view.layer.shadowColor = UIColor.black.cgColor
        view.layer.shadowOffset = CGSize(width: 3.0,height: 3.0)
        view.layer.shadowOpacity = 0.3
        view.layer.shadowPath = shadowPath.cgPath
    }
}

我们还可以通过设置shouldRasterize属性值为YES来强制开启离屏渲染。其实就是光栅化(Rasterization)。既然离屏渲染这么不好,为什么我们还要强制开启呢?当一个图像混合了多个图层,每次移动时,每一帧都要重新合成这些图层,十-分消耗性能。当我们开启光栅化后,会在首次产生一个位图缓存,当再次使用时候就会复用这个缓存。但是如果图层发生改变的时候就会重新产生位图缓存。所以这个功能一般不 能用于UITableViewCell中,cell的复 用反而降低了性能。最好用于图层较多的静态内容的图形。而且产生的位图缓存的大小是有限制的,一般是2.5个屏幕尺寸。在100ms之内不使用这个缓存,缓存也会被删除。所以我们要根据使用场景而定。

 

(3)其他的一些优化建议

●当我们需要圆角效果时,可以使用- -张中间透明图片蒙上去

●使用ShadowPath指定layer阴影效果路径

●使用异步进行layer渲染(Facebook开 源的异步绘制框架AsyncDisplayKit)

●设置layer的opaque值为YES,减少复杂图层合成

●尽量使用不包含透明(alpha) 通道的图片资源

●尽量设置layer的大小值为整形值

●直接让美工把图片切成圆角进行显示,这是效率最高的一种方案

●很多情况下用户.上传图片进行显示,可以让服务端处理圆角

●使用代码手动生成圆角Image设置到要显示的View.上,利用UIBezierPath (CoreGraphics框架)画出来圆角图片

(4) Core Animation工具检测离屏渲染

对于离屏渲染的检测,苹果为我们提供了一个测试工具Core Animation。可以在Xcode- >Open Develeper Tools->Instruments中找到,如下图:

 
page63image48021920
 Core Animation工具用来监测Core Animation性能,提供可见的FPS值,并且提供几个选项来测量渲染性能。如下图: 

下面我们来说明每个选项的功能:

Color Blended Layers:这个选项如果勾选,你能看到哪个layer是透明的,GPU正在做混合计算。显示红色的就是透明的,绿色就是不透明的。Color Hits Green and Misses Red:如果勾选这个选项,且当我们代码中有设置shouldRasterize为YES,那么红色代表没有复用离屏渲染的缓存,绿色则表示复用了缓存。我们当然希望能够复用。Color Copied Images:按照官方的说法,当图片的颜色格式GPU不支持的时候,Core Animation会拷贝一份数据让CPU进行转化。例如从网络.上下载了TIFF格式的图片,则需要CPU进行转化,这个区域会显示成蓝色。还有一种情况会触发Core Animation的copy方法,就是字体不对齐的时候。如下图:

色,如果勾选这个选项则移除10ms的延迟。对某些情况需要这样,但是有可能影响正常帧数的测试。Color Misaligned Ilmages:勾选此项,如果图片需要缩放则标记为黄色,如果没有像素对齐则标记为紫色。像素对齐我们已经在上面有所介绍。Color Offscreen-Rendered Yellow:用来检测离屏渲染的,如果显示黄色,表示有离屏渲染。当然还要结合Color Hits Green and Misses Red来看,是否复用了缓存。Color OpenGL Fast Path Blue:这个选项对那些使用OpenGL的图层才有用,像是GL KView或者CAEAGLL ayer,如果不显示蓝色则表示使用了CPU渲染,绘制在了屏幕外,显示蓝色表示正常。Flash Updated Regions:当对图层重绘的时候回显示黄色,如果频繁发生则会影响性能。可以用增加缓存来增强性能。

性能优化是一个持续的过程,未完待续。。。。。

猜你喜欢

转载自www.cnblogs.com/duzhaoquan/p/12421023.html