IOS之UITableView性能优化

前言

        说起优化,简直是博大精深。话不多说,笔者今天梳理的内容,UITableView的性能优化。先说一下tableview的执行顺序:

1.它会调用代理方法确定有几个分区

numberOfSectionsInTableView:

2.确定每个分区的表头高和表尾高(如果设定了HeardView和FooterView)

heightForHeaderInSection:

tableView:heightForFooterInSection:

3.确定每个分区有多少的cell

numberOfRowsInSection:

4.然后确定cell的高度

heightForRowAtIndexPath:

如果有多个section和row则循环执行上面的代码

5.以上信息确定完毕后及调用代理方法去获取cell

cellForRowAtIndexPath:

6.返回cell的高度

heightForRowAtIndexPath:

7.cell将要显示到屏幕上

willDisplayCell:forRowAtIndexPath:

8.cell超出屏幕进行服用时及会调用两次

heightForRowAtIndexPath:

然后在进行调用 5 . 6. 7 方法

一、cell的复用

        首先cell有两种复用方法:

                - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;  

                - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;  

        在iOS 6中dequeueReusableCellWithIdentifier:被dequeueReusableCellWithIdentifier:forIndexPath:所取代。如此一来,在表格视图中创建并添加UITableViewCell对象会变得更为精简而流畅。而且使用dequeueReusableCellWithIdentifier:forIndexPath:一定会返回cell,系统在默认没有cell可复用的时候会自动创建一个新的cell出来。

1.dequeueReusableCellWithIdentifier:(NSString *)identifier,如图:


2.dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath,如图:

        获取cell时如果没有可重用cell,如果cell为nib,将创建新的cell并调用其中的awakeFromNib方法;否则

调用cell中的initWithStyle:withReuseableCellIdentifier:方法创建新的cell。不过此方法,在创建tableview的时候,要注册cell,如图:


        不论以上哪两种方法,我们获取到cell后,可能习惯在cellForRowAtIndexPath:中为每一个cell绑定数据,实际上在调用cellForRowAtIndexPath:的时候cell还没有被显示出来,为了提高效率我们应该把数据绑定的操作放在cell显示出来后再执行,可以在tableView:willDisplayCell:forRowAtIndexPath:(以后简称willDisplayCell)方法中绑定数据。

        *注意*:

        willDisplayCell在cell 在tableview展示之前就会调用,此时cell实例已经生成,所以不能更改cell的结构,只能是改动cell上的UI的一些属性(例如label的内容等)。


二、cell的高度

        说到cell高度优化问题,可能大家都知道去计算并缓存cell的高度。今天说另一种方法。我们分为两种cell,一种是定高的cell,另外一种是动态高度的cell。

        (1)定高的cell,应该采用如下方式:

        self.tableView.rowHeight = 100;

        这个方法指定了所有cell高度都是100的tableview,rowHeight默认的值是44,所以一个空的TableView会显示成这个样子。对于定高cell,直接采用上面方式给定高度,不需要实现tableView:heightForRowAtIndexPath:以节省不必要的计算和开销。

        (2)动态高度的cell

        我们需要实现它的代理,来给出高度:

        -(CGFloat)tableView:(UITableView*)tableViewheightForRowAtIndexPath:(NSIndexPath*)indexPath{

                //return xxx;

        }

        这个代理方法实现后,上面的rowHeight的设置将会变成无效。在这个方法中,我们需要提高cell高度的计算效率,来节省时间。自从iOS8之后有了Self-Sizing cell的概念,cell可以自己算出高度,IOS11以后,Self-Sizing默认开启,包括Headers, footers。

        使用self-sizing cell需要满足以下三个条件:

        (1)使用Autolayout进行UI布局约束(要求cell.contentView的四条边都与内部元素有约束关系)。

        (2)指定TableView的estimatedRowHeight属性的默认值。

        (3)指定TableView的rowHeight属性为UITableViewAutomaticDimension。

        - (void)viewDidload {

            self.myTableView.estimatedRowHeight = 44.0;

            self.myTableView.rowHeight = UITableViewAutomaticDimension;

        }

三、cell的渲染

        为了保证TableView的流畅,当快速滑动的时候,cell必须被快速的渲染出来。所以cell渲染的速度必须快。如何提高cell的渲染速度呢?

        (1)当有图像时,预渲染图像,在bitmap context先将其画一遍,导出成UIImage对象,然后再绘制到屏幕,这会大大提高渲染速度。具体内容可以自行查找“利用预渲染加速显示iOS图像”相关资料

        (2)渲染最好时的操作之一就是混合(blending)了,所以我们不要使用透明背景,将cell的opaque值设为Yes,背景色不要使用clearColor,尽量不要使用阴影渐变等;

        (3)由于混合操作是使用GPU来执行,我们可以用CPU来渲染,这样混合操作就不再执行。可以在UIView的drawRect方法中自定义绘制;

        (4)减少subviews的个数和层级。子控件的层级越深,渲染到屏幕上所需要的计算量就越大;如多用drawRect绘制元素,替代用view显示;

        (5)少用subviews的透明图层。对于不透明的View,设置opaque为YES,这样在绘制该View时,就不需要考虑被View覆盖的其他内容(尽量设置Cell的view为opaque,避免GPU对Cell下面的内容也进行绘制);

         (6)避免CALayer特效(shadowPath)。 给Cell中View加阴影会引起性能问题,如下面代码会导致滚动时有明显的卡顿:

        view.layer.shadowColor= color.CGColor;

        view.layer.shadowOffset= offset;

        view.layer.shadowOpacity=1;

        view.layer.shadowRadius= radius;

        (7)我们在cell上添加系统控件的时候,实际上系统都会调用底层的接口进行绘制,大量添加控件时,会消耗很大的资源并且也会影响渲染的性能。当使用默认的UITableViewCell并且在它的ContentView上面添加控件时会相当消耗性能。所以目前最佳的方法还是继承UITableViewCell,并重写drawRect方法。

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

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

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

        (11)滑动时按需加载对应的内容

        参考文章

四、离屏渲染

        下面的情况或操作会引发离屏渲染:

        (1)、为图层设置遮罩(layer.mask);

        (2)、将图层的layer.masksToBounds / view.clipsToBounds属性设置为true;

        (3)、将图层layer.allowsGroupOpacity属性设置为YES和layer.opacity小于1.0;

        (4)、为图层设置阴影(layer.shadow *);

        (5)、为图层设置layer.shouldRasterize=true;

        (6)、具有layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的图层;

        (7)、文本(任何种类,包括UILabel,CATextLayer,Core Text等);

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

1、优化方案

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

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

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

        UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];

        imageView.image = [UIImage imageNamed:@"myImg"];

        //开始对imageView进行画图

        UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);

        //使用贝塞尔曲线画出一个圆形图

        [[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];

        [imageView drawRect:imageView.bounds];

        imageView.image = UIGraphicsGetImageFromCurrentImageContext();

        //结束画图

        UIGraphicsEndImageContext();

        [self.view addSubview:imageView];

优化方案2:使用CAShapeLayer和UIBezierPath设置圆角

        UIImageView *imageView=[[UIImageViewalloc]initWithFrame:CGRectMake(100,100,100,100)];

        imageView.image=[UIImageimageNamed:@"myImg"];

        UIBezierPath *maskPath=[UIBezierPathbezierPathWithRoundedRect:imageView.boundsbyRoundingCorners:UIRectCornerAllCornerscornerRadii:imageView.bounds.size];

        CAShapeLayer *maskLayer=[[CAShapeLayeralloc]init];

        //设置大小

        maskLayer.frame=imageView.bounds;

        //设置图形样子

        maskLayer.path=maskPath.CGPath;

        imageView.layer.mask=maskLayer;

        [self.viewaddSubview:imageView];

        对于方案2需要解释的是:

        CAShapeLayer继承于CALayer,可以使用CALayer的所有属性值;CAShapeLayer需要贝塞尔曲线配合使用才有意义(也就是说才有效果);使用CAShapeLayer(属于CoreAnimation)与贝塞尔曲线可以实现不在view的drawRect(继承于CoreGraphics走的是CPU,消耗的性能较大)方法中画出一些想要的图形,CAShapeLayer动画渲染直接提交到手机的GPU当中,相较于view的drawRect方法使用CPU渲染而言,其效率极高,能大大优化内存使用情况。总的来说就是用CAShapeLayer的内存消耗少,渲染速度快,建议使用优化方案2。

(2)shadow优化

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

        imageView.layer.shadowColor=[UIColorgrayColor].CGColor;

        imageView.layer.shadowOpacity=1.0;

        imageView.layer.shadowRadius=2.0;

        UIBezierPath *path=[UIBezierPathbezierPathWithRect:imageView.frame];

        imageView.layer.shadowPath=path.CGPath;

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

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

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

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

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

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

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

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

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容