tableView 卡顿优化,除了缓存计算高度,还可以...

4,469 阅读2分钟

列表界面的卡顿优化,除了缓存计算高度,

还可以开个线程,异步计算高度并缓存,

再回到主线程,刷新界面

还有图片解码消耗性能, ...

  • 使用纯代码,取代 xib ( xib, 一般会被解析成代码 )

  • 减少 UI 显示层级, ( zIndex 方向上 )

  • 合并用来显示的控件, ( UI 控件,数量上 )

例如:

一个 Label 的富文本 = 多个 Lable 的纯文本

  • 避免离屏渲染

避免使用到,离屏渲染缓冲区

( 苹果已经,优化得很好了 )

本文重点介绍,多线程大法

对于复杂的列表显示,

一般都是,滚动的时候,更卡顿

  • A 修改前,

IMG_1844.PNG

A 特点 0,没有缓存高度

cell 的高度,触发直接计算

先渲染,再取高度

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

    
    TimeLineCell *cell = [[TimeLineCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    
    [cell configureTimeLineCell:timeLineModel];
    
    [cell setNeedsUpdateConstraints];
    [cell updateConstraintsIfNeeded];
    
    [cell setNeedsLayout];
    [cell layoutIfNeeded];
    
    CGFloat rowHeight = 0;
    
    for (UIView *bottomView in cell.contentView.subviews) {
        if (rowHeight < CGRectGetMaxY(bottomView.frame)) {
            rowHeight = CGRectGetMaxY(bottomView.frame);
        }
    }
    
    return rowHeight;

}
A 特点 1,cell 中控件渲染的同时,在计算

一个 cell, 下面的图片展示相关

五种布局:

没有图片

单张

二三张

4 张

5 张及以上

for (UIButton *btn in self.contentImageBtns) {
             [btn removeFromSuperview];
         }
         [self.contentImageBtns removeAllObjects];
         if (timeLineModel.contentImages.count == 0) {
            [self.seperatorView mas_remakeConstraints:^(MASConstraintMaker *make) {
                 make.left.equalTo(self);
                 make.right.equalTo(self);
                 make.height.equalTo(@15);
                 make.top.equalTo(self.expandBtn.mas_bottom).offset(10);
            }];
         } else if (timeLineModel.contentImages.count == 1) {
             UIButton *contentImageBtn = [[UIButton alloc] init];
             [self.contentView addSubview:contentImageBtn];
             [contentImageBtn mas_makeConstraints:^(MASConstraintMaker *make) {
                 make.left.equalTo(self).offset(11);
                 make.top.equalTo(self.expandBtn.mas_bottom).offset(10);
                 make.width.equalTo(@250);
                 make.height.equalTo(@150);
             }];
             [self.contentImageBtns addObject:contentImageBtn];
         } else if (timeLineModel.contentImages.count == 2 || timeLineModel.contentImages.count == 3) {
             for (int i = 0; i < timeLineModel.contentImages.count; i++) {
                 UIButton *contentImageBtn = [[UIButton alloc] init];
                 [self.contentView addSubview:contentImageBtn];
                 [contentImageBtn mas_makeConstraints:^(MASConstraintMaker *make) {
                     make.left.equalTo(self).offset(11 + i * (10 + 90));
                     make.top.equalTo(self.expandBtn.mas_bottom).offset(10);
                     make.width.equalTo(@90);
                     make.height.equalTo(@90);
                 }];
                 [self.contentImageBtns addObject:contentImageBtn];
             }
         } else if (timeLineModel.contentImages.count == 4) {
             for (int i = 0; i < 2; i++) {
                 for (int j = 0; j < 2; j++) {
                     UIButton *contentImageBtn = [[UIButton alloc] init];
                     [self.contentView addSubview:contentImageBtn];
                     [contentImageBtn mas_makeConstraints:^(MASConstraintMaker *make) {
                         make.left.equalTo(self).offset(11 + j * (10 + 90));
                         make.top.equalTo(self.expandBtn.mas_bottom).offset(10 + i * (10 + 90));
                         make.width.equalTo(@90);
                         make.height.equalTo(@90);
                     }];
                     [self.contentImageBtns addObject:contentImageBtn];
                 }
             }
         } else if (timeLineModel.contentImages.count == 5 || timeLineModel.contentImages.count == 6 || timeLineModel.contentImages.count == 7 || timeLineModel.contentImages.count == 8 || timeLineModel.contentImages.count == 9) {
             for (int i = 0; i < timeLineModel.contentImages.count; i++) {
                     UIButton *contentImageBtn = [[UIButton alloc] init];
                     [self.contentView addSubview:contentImageBtn];
                     [contentImageBtn mas_makeConstraints:^(MASConstraintMaker *make) {
                         make.left.equalTo(self).offset(11 + (i % 3) * (10 + 90));
                         make.top.equalTo(self.expandBtn.mas_bottom).offset(10 + (i / 3) * (10 + 90));
                         make.width.equalTo(@90);
                         make.height.equalTo(@90);
                     }];
                     [self.contentImageBtns addObject:contentImageBtn];
             }
         }

改进关键点是,手动计算 Frame , 停止使用 Auto layout

( Auto layout ,也会被计算为控件的 Frame )

还有,开辟线程计算富文本,与缓存高度

  • 修改后,B FPS 从 37 到 58。 进步是巨大的

IMG_1846.PNG

B 改进 0,将布局信息的计算,与渲染分离

将布局信息的计算,放在子线程,

控件的渲染,放在主线程

让主线程保持,相对的轻松

 dispatch_async(dispatch_get_global_queue(0, 0), ^{
            // 子线程处理数据
              NSData *data = // ...
              NSDictionary *dicJson=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
           
                for (id json in dicJson[@"data"]) {
                    TimeLineModel *timeLineModel = [TimeLineModel yy_modelWithJSON:json];
                    [self.timeLineModels addObject:timeLineModel];
                }
            
               for (TimeLineModel *timeLineModel in self.timeLineModels) {
                   TimeLineCellLayout *cellLayout = [[TimeLineCellLayout alloc] initWithModel:timeLineModel];
                   [self.layouts addObject:cellLayout];
               }
           
               dispatch_async(dispatch_get_main_queue(), ^{
                    // 主线程,显示数据
                    [self.timeLineTableView reloadData];
               });

       });
B 改进 1,手动布局,取代自动布局

一个 cell, 下面的图片展示相关

把上面同样功能的自动布局代码,中的布局信息,

抽离出来

self.imageRects = [NSMutableArray array];
    if (_timeLineModel.contentImages.count > 0){
        if (_timeLineModel.contentImages.count == 1) {
            CGRect imageRect = CGRectMake(11, CGRectGetMaxY(self.expandRect) + 10, 250, 150);
            [self.imageRects addObject:@(imageRect)];
        } else if (_timeLineModel.contentImages.count == 2 || _timeLineModel.contentImages.count == 3) {
            for (int i = 0; i < _timeLineModel.contentImages.count; i++) {
                CGRect imageRect = CGRectMake(11 + i * (10 + 90), CGRectGetMaxY(self.expandRect) + 10, 90, 90);
                [self.imageRects addObject:@(imageRect)];
            }
        } else if (_timeLineModel.contentImages.count == 4) {
            for (int i = 0; i < 2; i++) {
                for (int j = 0; j < 2; j++) {
                    CGRect imageRect = CGRectMake(11 + j * (10 + 90), CGRectGetMaxY(self.expandRect) + 10 + i * (10 + 90), 90, 90);
                    [self.imageRects addObject:@(imageRect)];
                }
            }
        } else if (_timeLineModel.contentImages.count == 5 || _timeLineModel.contentImages.count == 6 || _timeLineModel.contentImages.count == 7 || _timeLineModel.contentImages.count == 8 || _timeLineModel.contentImages.count == 9) {
            for (int i = 0; i < _timeLineModel.contentImages.count; i++) {
                CGRect imageRect = CGRectMake(11 + (i % 3) * (10 + 90), CGRectGetMaxY(self.expandRect) + 10 + (i / 3) * (10 + 90), 90, 90);
                [self.imageRects addObject:@(imageRect)];
            }
        }
    }
B 改进 2, 缓存高度

两个层面,

首先,缓存了 cell 的高度

其次,缓存了 cell 中,各个控件的位置信息 frame

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
     return  self.layouts.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
       TimeLineCell *cell = [tableView dequeueReusableCellWithIdentifier:ResuseID];

       [cell configureLayout:self.layouts[indexPath.row]];

        return cell;
}
* 图片,也挺重要的

网络下载图片数据,到手机屏幕上可视的位图,

有一个解码的过程

这个,挺消耗性能。应该放在子线程中,处理

SDWebImage 帮你做了

此时,还用到了自动释放池

@autoreleasepool{ }

总结,常用的实践,就是

对于复杂的界面,我们拿到网络请求到的数据后,

一般不会直接到主线程,一边计算,一边刷新 UI

  • 而是,开个线程,

在开辟的线程里面,计算出 UI 需要的信息

( 就是在这个子线程,把能做的,全部做掉 )

最后,回到主线程,刷新 UI

区别是,主线程操作的事件,明显轻松了很多

缺点是,开发代码,有增长。

程序性能,与开发效率,存在一个平衡

github repo