移动开发干货店iOS UI开发基础知识

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

2019-03-19  本文已影响98人  晴天ccc

简介:

UITableView我想大家都不陌生,他是UIKit一个重要组件。可以用来展示数据列表,或者灵活使用进行页面布局。
其使用中遵循MAC模式,数据模型(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,不要阻塞主线程

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

上一篇下一篇

猜你喜欢

热点阅读