UITableView 性能优化

UITableView 工作流程:

How to get cell in heightForRowAtIndexPath?

It’s impossible, because when -heightForRowAtIndexPath is called, no cells are created yet. You need to understand how the UITableView works:

  1. UITableView asks it’s datasource how many sections it will have

    1
    -numberOfSectionsInTableView

    At this point there are no cells created.

  2. UITableView asks it’s datasource how many rows each section will have

    1
    -numberOfRowsInSection

    At this point there are no cells created.

  3. UITableView asks it’s delegate height of each visible row, to know where cells will be located

    1
    -heightForRowAtIndexPath

    At this point there are no cells created.

  4. UITableView asks it’s datasource to give it a cell to display at given index path

    1
    -cellForRowAtIndexPath

    At this point the cell is created.

UITableView性能优化:

  • cell复用
    其实就是使用一个identifier注册一下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    static NSString *Identifier = @"cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (!cell) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
    }

    return cell;
    }
  • 减少cell高度计算次数
    分两种情况:

    • cell高度固定

      1
      tableView.rowHeight = 88
    • 动态高度的cell
      一般在代理里实现高度计算

      1
      2
      3
      - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
      // return cellHeight;
      }

      这里对应indexPath的cell高度算出来后缓存下,避免下次重复计算,因为一些高度计算也是比较耗时的
      如果cell需要请求网络图片,可以这样处理

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

      ...

      [cell.imageView setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
      placeholderImage:[UIImage imageNamed:@"placeholder.png"]
      success:^(UIImage *image, BOOL cached) {

      // save height of an image to some cache
      [self.heightsCache setObject:[NSNumber numberWithFloat:imHeight]
      forKey:urlKey];

      [tableView beginUpdates];
      [tableView reloadRowsAtIndexPaths:@[indexPath]
      withRowAnimation:UITableViewRowAnimationFade];
      [tableView endUpdates];
      }
      failure:^(NSError *error) {... failure code here ...}];

      }

      - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
      // try to get image height from your own heights cache
      // if its is not there return default one
      CGFloat height = [[self.heightsCache objectForKey:urlKeyFromModelsArrayForThisCell] floatValue];
      ...
      return newHeight;
      }
  • cell里面图片处理
    如果是网络图片,使用异步请求
    通过UIImage或CGImageSource创建的图片并没有立刻解码。图片在设置到UIImageView或CALayer.contents中,并且CALayer被提交到GPU前,CGImage中数据才会解码。解码这步很耗时,可以在后台把图片绘制到CGBitmapContext,然后从Bitmap直接创建图片(解码后)。常见的网络图片库都有这个功能。

  • 减少View的个数
    在cell上添加过多的view会影响渲染性能,可以重写cell的drawrect,一些简单控件直接画出来

  • 不要动态添加subview,初始化时直接创建所有view,按需hide

  • 少使用阴影(shadow)、圆角(cornerRadius)、遮罩(mask)
    这些属性会引发离屏渲染(GPU在当前屏幕缓冲区外新开辟一个渲染缓冲区进行工作,也就是离屏渲染,这会给我们带来额外的性能损耗,如果这样的圆角操作达到一定数量,会触发缓冲区的频繁合并和上下文的的频繁切换,性能的代价会宏观地表现在用户体验上——掉帧。)。为了避免这种情况,可以尝试开启 CALayer.shouldRasterize 属性,但这会把原本离屏渲染的操作转嫁到 CPU 上去。对于只需要圆角的某些场合,也可以用一张已经绘制好的圆角图片覆盖到原本视图上面来模拟相同的视觉效果。最彻底的解决办法,就是把需要显示的图形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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];
  • 设置layer的opaque值为YES,减少复杂图层合成