一次 TableView 性能优化经历

jopen 9年前

 

一次 TableView 性能优化经历

作者:@__weak_Point 。

题外话

前段时间才换了工作,从面试准备到入职新公司,大概有半个多月时间吧,感慨颇深。找工作,太多运气成分在里面。有一些话想分享给大家:1.多认识一些这个行业的朋友,说不定你的下份工作就是其中的一个朋友介绍的。2.最好不要在7、8月份换工作,因为真的很热。

缘由

来到新公司后,一开始是熟悉项目、代码,以及改bug(填坑),然后上周开始做新的功能(准备挖坑)。生活不就是不停的挖坑和填坑吗?!遇到了一个Tableview卡帧的问题,花了点时间才解决,记录一下吧。好了,废话不多说,先上张效果图:

一次 TableView 性能优化经历

ps:其实是仿照nice的照片详情浏览效果

现在的照片详情页面是一个单独的页面(vc),用户想看其他的照片详情,需返回上一级页面,再点击进来,然后下个版本产品想改成上面那种效果。当时我想到两种方案:

一:用一个倾斜90°的tableview来做,简单,不用自己维护重用队列,每个cell放一个 vc 的view 就可以了,so easy。但是后面出现了问题,没记太清,当时也忘了截图,就换用第二种方案。

二:用scrollView来写,自己来维护重用队列,具体做法大家可以参考 UIScrollView 实践经验 (3.重用) 。最后“完美”地实现了需求,开始做别的需求去了。

因为当时在模拟器上开发,也没想到真机上会卡帧。过了1天,这个功能提交给测试,然后就发现了问题:在scrollView滚动的时候,明显的感觉到了卡帧,然后就开始优化。

ps:有关TableView的效果一定要跑真机!!有关TableView的效果一定要跑真机!!有关TableView的效果一定要跑真机!! (重要的事说三遍)

卡帧猜想

因为也没有仔细看那个vc以及cell中的代码,就大概猜想了一下卡帧的原因:

1.尼玛,该不会是 UIScrollView的重用 没写好?

断点验证了下,vc只会创建3个,重用没问题呀。

2.因为涉及重用,所有vc里面tableview的内容肯定不是一下子全请求出来的,每滚动一次才会去请求下个页面的数据,以及初始化页面。然后再看 nice,忽然发现它滚动的时候,状态栏居然没有网络请求的小菊花!!难不成是一次请求的?应该不会吧,这么多数据呀。为了验证这种猜想,用 Charles 拦截下,结果nice也是每滚动次发次请求的:

一次 TableView 性能优化经历

iOS开发工具-网络封包分析工具Charles

3.这个时候我又想到去搜nice的iOS工程师的github 和 博客,可惜github不能搜组织,就在微博搜了下

一次 TableView 性能优化经历

一次 TableView 性能优化经历

(互相关注 是后来事)

一次 TableView 性能优化经历

最后找到了他的博客,但是可惜没有找到我想要的。。。

进入正题

不管什么原因,先跑下Instruments三件套吧(Time Profiler,Core Animation,GPU Driver)

一次 TableView 性能优化经历

性能调优

好嘛,真是卡,一个一个看吧

1.首先排除了GPU的问题

一次 TableView 性能优化经历

2.CPU

一次 TableView 性能优化经历

这算多吗?我不太确定,对比 上面性能调优一文中的这段

一次 TableView 性能优化经历

得了,还是看那个vc里面是怎么写得吧??

网络请求

- (void)refreshData  {    HBStoryDetailFetcher *fetcher = [[HBStoryDetailFetcher alloc] init];    fetcher.parameters = @{@"id": _story_id};    [self runFetcher:fetcher forView:self.view success:^{      _story = [fetcher.story mutableCopy];      [self refreshCommentList];    } failure:^(NSError *error){      [self refreshCommentList];    }];  }  - (void)refreshCommentList {    HBCommentListFetcher *fetcher = [[HBCommentListFetcher alloc] init];    fetcher.parameters = @{@"story_id": _story_id};    [self runFetcher:fetcher forView:self.view success:^{      _page = 0;      _comments = [[NSMutableArray alloc] init];      [_comments addObjectsFromArray:fetcher.comments];      [_tableView reloadData];      if (fetcher.comments.count == 20) {        [self addLoadMore];      }    } failure:^(NSError *error){      [_tableView reloadData];    }];  }  UITableViewDataSource  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    if (indexPath.row == 0) {      HBStoryDetailCell *cell = [tableView dequeueReusableCellWithIdentifier:@"feedHomeCell" forIndexPath:indexPath];      cell.parent = self;      cell.isStoryDetailView = YES;      cell.indexPath = indexPath;      cell.story = _story;      cell.delegate = self;      [cell updateUI];      return cell;    }    else    {      HBStoryCommentCell *cell = [tableView dequeueReusableCellWithIdentifier:@"commentCell" forIndexPath:indexPath];      cell.parent = self;      cell.indexPath = indexPath;      cell.story = [_comments[indexPath.row-1] mutableCopy];      [cell updateUI];      return cell;    }  }  UITableViewDelegate  - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {    if (indexPath.row == 0) {      return [HBStoryDetailCell calculateHeightForStory:_story] - [HBStoryDetailCell commentLabelHeight:_story];    }    else    {      return [HBStoryCommentCell calculateHeightForStory:_comments[indexPath.row-1]];    }  }