转载

一次 TableView 性能优化经历(iOS)

题外话

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

缘由

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

一次 TableView 性能优化经历(iOS)

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)

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

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

一次 TableView 性能优化经历(iOS)

一次 TableView 性能优化经历(iOS)

(互相关注 是后来的事)

一次 TableView 性能优化经历(iOS)

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

进入正题

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

一次 TableView 性能优化经历(iOS)

性能调优

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

1.首先排除了GPU的问题

一次 TableView 性能优化经历(iOS)

2.CPU

一次 TableView 性能优化经历(iOS)

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

一次 TableView 性能优化经历(iOS)

得了,还是看那个vc里面是怎么写得吧�� > 要声明一点的是,我们项目中没有用到model,用的是字典������

// 网络请求 - (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]];  } } 

首先,cell的高度没有缓存,这肯定有影响。另外,在打断点调试的过程中,发现 refreshData 的success block回调居然执行2次,这岂不是意味着 tableviewreloadData 两次,短时间刷新2次,肯定会卡啊,继续往里面看

- (void)runWithSuccess:(void (^)(id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *))failure {  HBLog(@"正在加载:%@",_requestURL);  //直接从网上加载数据  if (HBCachePolicyNetworkOnly == _cachePolicy) {   [self loadFromNetWorkWithSuccess:success failure:failure];   return;  }  //先从缓存中取出数据,把结果返回,再判断缓存数据是否过期,如果过期,则再从网上取,取到后把结果后返回  if (HBCachePolicyCacheElseNetwork == _cachePolicy ) {   [self loadFromCacheWithSuccess:success failure:failure];   if (self.cacheHasExpired) {    [self loadFromNetWorkWithSuccess:success failure:failure];   }  } } - (BOOL)cacheHasExpired{  NSDate *LoadFromNetworkFinishTime = [self getObjectFromCacheWithFileName:_dateFileName];  if (nil == LoadFromNetworkFinishTime) {   return YES;  }  if ([[NSDate date] timeIntervalSinceDate:LoadFromNetworkFinishTime]>_maxCacheAge * 60){   return YES;  }  return NO; } 

然后发现 @property (assign,nonatomic) float maxCacheAge; //缓存过期时间 单位分钟 默认是0的,这尼玛缓存分分钟过期啊,不过想想之前是一个单独的vc,这样做也挺好的,不过现在滚动中请求就有点不好了。先把数据请求设置成只从网络中取吧,看看效果先。

一次 TableView 性能优化经历(iOS)

一次 TableView 性能优化经历(iOS)

请相信我快速滑动的速度是一样一样滴����

一次 TableView 性能优化经历(iOS)

区域1是快速滑动的,感觉还可以,帧数也比较稳定。区域2是慢滑,在滚动到中间的时候还是感到有点小卡。不过现在已经好很多了,再接着优化,重点排查这几个方法:

一次 TableView 性能优化经历(iOS)

iOS性能优化

正文到此结束
Loading...