iOS-UITableView重用机制和性能优化、

简介:

UITableView我想大家都不陌生,他是UIKit一个重要组件。可以用来展示数据列表,或者灵活使用进行页面布局。
其使用中遵循MVC模式,数据模型(NSObject)、视图(UIView)、控制器(UITableViewController)分离。 点击前往Github下载Demo

官方文档使用:

// 注册方式
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"identifier"] ;
//返回每一组的每一行显示什么内容
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    // 定义一个重用标示,用static修饰。就放在了内存的静态区了。
    static NSString *identifier = @"Cell";
    
    // 缓存池中寻找是否有可以重用的cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    
    // 如果缓存池中没有ID,创建一个cell,并给它一个重用标示
    if (cell == nil) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    }
    
    // 设置数据,赋值给cell
    
    return cell;
}

为了做到显示和数据分离,iOS TableView的实现并不是为每一个数据创建cell,而是创建屏幕可显示最大个数+1的cell,对cell做单独显示配置,来达到既不影响显示效果,又能充分节约内容的目的。


重用机制原理:

image

机制

假设虚线范围是屏幕的显示区域,整个屏幕里面每个Cell的identifier是一样的。

A2、A6 的Cell有一部分是在屏幕内。

A3、A4、A5的Cell全部在屏幕内。

系统会创建当前屏幕Cell个数+1的Cell,A1在屏幕外,现在它就被放到了重用池;

向上滑动时候,新的CellA7就会去重用池里面根据指定indentifier取出A1存放的Cell。

就如同盘子使用了之后,洗完可以继续使用。

作用:

避免大量创建实例对象,减少Memory Warning内存的消耗甚至Crash掉,从而提高滑动流畅性,提高用户体验!


UITableView的性能优化

一般开发过程中遇到的几个重要的问题:

cellForRowAtIndexPath:方法中处理了过多的业务。
cellheight动态变化时计算方式不对。
tableviewCellsubview层级太复杂,做了大量透明处理。

1、cell的数据绑定

在使用的过程中我们注意到cellForRowAtIndexPath:中为每一个cell绑定数据,在实际调用中cellForRowAtIndexPath:时候cell还没有被显示出来,为了提高效率应该把绑定数据操作放在cell显示后再执行,可以在tableView:willDisplayCell:forRowAtIndexPath:方法中绑定数据。

- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *identifier = @"Cell"; 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 
    if (cell == nil) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    } 
    return cell;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    // model类在setter方法中进行设置也可。 
    cell.textLabel.text = @"这里进行数据绑定";
}

2、cell高度计算

cell的高度分为两种,一种是常用的固定高度,一种类似微博首页的动态计算高度。

(1)固定高度Cell

self.tableView.rowHeight = 55;

这个方法指定了所有cell高度都是55,rowHeight默认的值是44。对于定高cell,直接采用上面方式给定高度,不需要实现tableView:heightForRowAtIndexPath: 以节省不必要的计算和开销【重要】。

(2)动态计算高度Cell
动态计算高度,我们需要用到heightForRowAtIndexPath方法,这个代理方法实现之后,rowHeight设置将会变成无效。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 55 ;
}

思考:
类似微博项目根据传入那些动态的元素(文字,图片等),然后返回计算后的高度。这样没有问题,只是计算量很复杂,每次reloadData,【UITableView在每次reloadData的时候都要刷新所有cell高度,如果你有100行cell,代理就会执行100次cell高度,而不是屏幕显示cell的数量的高度。】很消耗性能。

方法1:
iOS8之后,有了self-sizing cell的概念,cell可以自己算出高度,不过使用起来会有三个条件:

  • 使用Autolayout进行UI布局约束(要求cell.contentView的四条边都与内部元素有约束关系)。
  • 指定TableView的estimatedRowHeight属性的默认范围值。
  • 指定TableView的rowHeight属性为UITableViewAutomaticDimension。
   self.tableView.estimatedRowHeight = 55.0;
   self.tableView.rowHeight = UITableViewAutomaticDimension;

方法2:
赋值和计算布局分离,在服务器异步获得数据之后,根据数据源计算出对应的布局,并缓存到数据源中。这样在tableView:heightForRowAtIndexPath:方法中就直接返回高度,而不需要每次都计算了。
新建一个dataModel类,新建

@property (nonatomic, assign) CGFloat cellViewH;

- (CGFloat)cellViewH {
    // 写入你的业务逻辑根据不同内容变化高度
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        dataModel *model = self.[indexPath.row];
        return model.cellViewH;
}

这样的方法基本能满足简单界面,但是针对朋友圈图文混排,这样还是需要继续优化的。

方法3:

TODO

3、页面

方法1、圆角优化

我们一般设置圆角的方式如下:

self.imageView.layer.cornerRadius=CGFloat(10);
self.imageView.layer.masksToBounds=YES;

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

方案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=[[UIImageView alloc]initWithFrame:CGRectMake(100,100,100,100)];
    imageView.image=[UIImage imageNamed:@"myImg"];
    UIBezierPath *maskPath=[UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];
    CAShapeLayer *maskLayer=[[CAShapeLayer alloc]init];
    maskLayer.frame=imageView.bounds;
    maskLayer.path=maskPath.CGPath;
    imageView.layer.mask=maskLayer;
    [self.view addSubview:imageView];

方法2、滑动时按需加载对应的内容

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

-(void)scrollViewWillEndDragging:(UIScrollView *)scrollViewwithVelocity(CGPoint)velocitytargetContentOffset(inoutCGPoint *)targetContentOffset{
   // 代码待整理 
}

方法3、定义一种(尽量少)类型的Cell,擅长使用hidden隐藏(显示)

分析cell结构,尽可能将相同内容抽取到一种样式的cell,这样虽然cell的体积会大很多,但是数量不会多。这样的好处:

* 减少代码量,减少XIB文件的数量,容易修改和维护。
* 基于cell重用,运行时铺满屏幕所需cell数量固定N个,如果只有一种cell,那就是N个cell实例,如果M中cell,可能会是MN个cell实例,相比之下占用更多内存。

方法4、使用不透明视图

不透明的视图可以极大地提高渲染的速度。因此如非必要,可以将table cell及其子视图的opaque属性设为YES(默认值UIButton内部的label的opaque默认值都是NO])。
Cell中不要使用clearColor,无背景色,透明度也不要设置为0。

方法5、使用局部更新

如果只是更新某组的话,使用reloadSection进行局部更新

方法6、不要给cell动态添加subView

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

方法7、异步化UI,不要阻塞主线程

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

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

推荐阅读更多精彩内容