今天测试反馈了一个问题,有一个列表每次下啦刷新内存都在持续增加,最后因为OOM崩溃了。
这个页面是个双层的TableView,就是外层的每个Cell里面又装了一个TableView。
开始以为是循环引用导致的不释放问题,结果用Instrument查看并没有内存泄漏。
查了差不多一个小时,发现原来是外层的TableView的CellForRows的时候,cell都没有重用,这也就导致每次在CellForRow的方法里面都在持续创建一个新的Cell。又因为新的Cell里面是TableView,所以数据增加的特别快。
那么到这里问题就差不多解决了,用回系统的reuseIdentifier
方法。
但是对应的开发说,外层的Cell比较特殊,不能用TableView的重用池管理。
那么对这种情况,直接自己维护一个Cell的重用池好了。
我们创建一个Cell的可变字典:
@property (nonatomic, strong) NSMutableDictionary *cellsDic;
- (NSMutableDictionary *)cellsDic {
if(!_cellsDic) {
_cellsDic = [NSMutableDictionary dictionary];
}
return _cellsDic;
}
然后我们在cellForRowAtIndexPath:
方法里面这么改造:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
BaseCellData *cellData = self.viewModel.dataList[indexPath.row];
NSString *cellType = [cellData.cellType.identifier stringByAppendingString:[NSString stringWithFormat:@"-%@-%@",@(indexPath.section),@(indexPath.row)]];
BaseTableViewCell *cell = [self.cellsDic valueForKey:cellType];
if (!cell) {
cell = [[DeviceRelevanceTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellType];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[self.cellsDic setValue:cell forKey:cellType];
}
[cell updateCellWithData:cellData];
[cell hideLastCellSeperator:YES];
return cell;
}
这样子整个TableView的Cell重用池就有我们自己来维护了。
TableView重用的原理
在UITableView头文件中,可以找到NSMutableArray* visiableCells,和NSMutableDictnary* reusableTableCells两个结构。visiableCells内保存当前显示的cells,reusableTableCells保存可重用的cells。
TableView显示之初,reusableTableCells为空,那么tableView dequeueReusableCellWithIdentifier:CellIdentifier返回nil。开始的cell都是通过[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]来创建,而且cellForRowAtIndexPath只是调用最大显示cell数的次数。
实际上TableView并不是只有在滑动过程中才会重用reusableTableCells的表,一些其他情况也会对Cell进行重用:
1. reloadData
这种情况比较特殊。一般是部分数据发生变化,需要重新刷新cell显示的内容时调用。在cellForRowAtIndexPath调用中,所有cell都是重用的。我估计reloadData调用后,把visiableCells中所有cell移入reusableTableCells,visiableCells清空。cellForRowAtIndexPath调用后,再把reuse的cell从reusableTableCells取出来,放入到visiableCells。
2.reloadRowsAtIndex
刷新指定的IndexPath。如果调用时reusableTableCells为空,那么cellForRowAtIndexPath调用后,是新创建cell,新的cell加入到visiableCells。老的cell移出visiableCells,加入到reusableTableCells。于是,之后的刷新就有cell做reuse了。
网友评论