记得上一次装逼过头地说,cell我不复用照样能运行,不是复用么,我偏不复用,每次都创建新的,结果导致的问题可想而知,项目里的item数目少还好说,一旦超过一定数量就难以支撑了,甚至手机发烫直接闪退......当然那只是一时不服气的说道,小飞哥知道我这个坎没过去,他说迟早还会碰壁的,让我自己深刻一次,我想就是这一次吧. 还好这个问题我自己及时发现并逐步解决。想想都有点刺激,虽然再也碰不到第二次了。 下面是记录我这一次修复bug的心路历程。
iOS开发中最最常用到的功能良好,API封装优秀的两大组件,分别是UITableView
和UICollectionView
,它们都有一个共同的特性,也可以说是优点,那就是cell的复用机制。
-
cell的复用机制
无论是UITableView
还是UICollectionView
,它们都是属于列表的形式而存在,由于手机屏幕有限,很多时候需要滚动来实现阅读到更多的内容,这和设计理念也有关,大量规则有序的重复的样式或者图片文字的摆放,我们可以创建一个类似于模板的东西那就是cell
,cell
的复用就是使用模板的过程,比如说一个列表里有很多种样式,那就是很多种cell
,每种样式匹配不同的cell
模板。
一般来说一个屏幕只能容纳有限个cell
,假如数据源里有很多同一种模型数据,需要得到展示,也就是说刚一开始的时候看到的那几个cell,在滚动过程中(假如是从下往上滚,当然从上往下滚也是同样的道理)在屏幕顶部的一个cell完全滚出屏幕外以及屏幕底部滚出来一个cell到屏幕里,就是这个过程发生的事情。cell有一个缓存池类似于一个容器,将不需要用到的空闲cell装进这个容器,即滚粗屏幕外的cell;需要用到类似模板的cell的时候,从这个cell的缓存池容器里取,即从底部滚到屏幕里的cell
cell复用的错误示范,如图重影
❌反面教程
/** 一段是一个类似于微信朋友圈或者微博的列表cell,这里是存在问题的 */
- (UNTopicListTableViewCell *)createHavePhotosCellWithTopicFrameModel:(UNTopicListCellFrame *)topicFrame tableView:(UITableView *)tableView{
UNTopicListTableViewCell *cell = nil; ////这一句和”!cell”是等效的,所以,这样每次都去创建cell,导致内存疯长,一度达到了738MB之多
/// TODO !!! 图片一样多才复用好嘛
if (self.photos.count == topicFrame.topicModel.picurls.count) {
cell = [tableView dequeueReusableCellWithIdentifier:[UNTopicListTableViewCell resuseidentifyingCellID]];
}
if (!cell) {
[self.photos removeAllObjects];
cell = [[UNTopicListTableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier:[UNTopicListTableViewCell resuseidentifyingCellID]];
}
// 给cell传递模型数据
cell.topicFrame = topicFrame;
///创建图片浏览
[self createPhotoBrowserWithTopicModel: topicFrame.topicModel topicCell: cell];
return cell;
}
进一步优化---~
/** 先去缓存池里去取,取不到就去创建,创建需要判断,如果有一样的模板cell,就可以复用,不要重复去创建,图片数量不一样多cell长得不一样,那就重新创建一个,大致逻辑就这样 */
- (UNTopicListTableViewCell *)createHavePhotosCellWithTopicFrameModel:(UNTopicListCellFrame *)topicFrame tableView:(UITableView *)tableView{
UNTopicListTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:[UNTopicListTableViewCell resuseidentifyingCellID]];
if (self.photos.count != topicFrame.topicModel.picurls.count) {
if (!cell) {
[self.photos removeAllObjects];
cell = [[UNTopicListTableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier:[UNTopicListTableViewCell resuseidentifyingCellID]];
}
}
// 给cell传递模型数据
cell.topicFrame = topicFrame;
///创建图片浏览
[self createPhotoBrowserWithTopicModel: topicFrame.topicModel topicCell: cell];
return cell;
}
再次优化
基于前两步只是解决了内存暴涨的问题,但是复用问题没有解决,看了网红大牛的分享,都说好文章都是改出来的,因为前面的经验缺乏,很多的细节部分考虑不周,这样暴露出来的问题也就越多,有问题就得解决嘛,所以一步一步来,你想要的时间都会给你...
- (UNClubCommentListTableViewCell *)createHavePhotosCellWithEvaluteFrameModel:(UNCommentFrame *)evaluateframe tableView:(UITableView *)tableView{
/** 这一点很关键,之前帖子的是固定最多三张图,所以没换行,
没察觉出来,这个出现cell图片重影现象,
一下子让我定位出来是复用的bug,复用的cell不正确,
一定是celIID没搞好,所以有多少张图片就以图片张数作为cellID的依据,这样很like*/
NSString *photoCellID = [NSString stringWithFormat:@"the%ld_CellID",evaluateframe.model.images.count];
UNClubCommentListTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: photoCellID];
if (self.photos.count != evaluateframe.model.images.count) {
[self.photos removeAllObjects];
if (!cell) {
cell = [[UNClubCommentListTableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: photoCellID];
cell.separatorInset = UIEdgeInsetsMake(0, 15, 0, 15);
}
}
// 给cell传递模型数据
cell.commentFrame = evaluateframe;
///创建图片浏览
[self createPhotoBrowserWithEvaluteModel: evaluateframe.model evaluteCell: cell];
return cell;
}
![😓]再次优化cell图片错乱的问题 (一次次打脸,再也不写什么正确姿势了,每一次的更新就是在纠正自己)
优化列表图片与图片浏览器看到的图片不一致,cell复用成功了,cell图片个数和ID保持数据源图片张数一致,但是不明白为啥还是出现了重复的图片,有残留?我也不确定是不是SDwebImage的缓存,之后网上查了一圈,发现没有啥特别好的解决方案. 只能是把子视图移除重新创建一遍,但是整一个cell还是复用的,局部不复用
于是,有了局部不复用的操作:
在cell里不需要在和图片浏览器的数组数据源保持一致了,因为一开始就想错了.图片浏览器数据源是在点击cell的某一张图时加进去的,而cell是网络请求获取到数据源时进行展示的,动作与时机不同,不能混为一谈。
- (UNClubCommentListTableViewCell *)createHavePhotosCellWithEvaluteFrameModel:(UNCommentFrame *)evaluateframe tableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath{
UNCommentFrame *commentFrame = self.dataSource[indexPath.row];
static NSString *photoCellID = @"photoCellID";
UNClubCommentListTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: photoCellID];
if (!cell) {
cell = [[UNClubCommentListTableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: photoCellID];
cell.separatorInset = UIEdgeInsetsMake(0, 15, 0, 15);
}
cell.commentFrame = commentFrame;
///创建图片浏览
[self createPhotoBrowserWithEvaluteModel: commentFrame.model evaluteCell: cell];
return cell;
}
移除子视图上的图片,每次赋值重新创建
- (void)setPhotos:(NSArray *)photos{
_photos = photos;
[self removeAllSubviews];
NSUInteger photosCount = photos.count;
// 创建足够数量的图片控件
// 这里的self.subviews.count不要单独赋值给其他变量
while (self.subviews.count < photosCount) {
NSInteger index = self.subviews.count;
UIImageView *photoView = [[UIImageView alloc] init];
photoView.contentMode = UIViewContentModeScaleToFill;
photoView.userInteractionEnabled = YES;
Weak(weakSelf);
[photoView addTapActionWithBlock:^(UIGestureRecognizer *gestureRecoginzer) {
BLOCK_EXEC(weakSelf.clickCommentImgPage,index);
}];
photoView.image = nil;
[photoView sd_setImageWithURL:[NSURL URLWithString: [photos[index] jj_200X200_Image]] placeholderImage:[UIImage imageNamed:@"suprise"] options:SDWebImageRefreshCached];
[self addSubview:photoView];
}
}
总结: 爬坑的过程虽然是异常艰辛的,为了不重复沦陷,就是再简单的东西还是要记录一下自己的心得,下次别人面试问我1加1等多少的时候,我就可以确切地告诉他等于2。
参考博客:
iOS之TableViewCell重用机制避免重复显示问题
ios开发之解决重用TableViewCell导致的界面错乱的问题