在iOS的开发中,UITableView是最经常用到的UI组件之一。然而,UITableView却非常容易引起「NSInternalInconsistencyException(数据不一致)」的崩溃。其中,调用reloadSections时,一不留神就会引发崩溃。
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 2. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
reloadSections简介
当UITableView的数据源变化时,通常会调用reloadData
或者reloadSections:withRowAnimation:
通知UITableView刷新UI。单从UI角度考虑,两者最大的区别就是reloadData
没有动画。所以,一般为用户体验考虑,我一般使用reloadSections:withRowAnimation:
。
reloadSections引发崩溃
调用reloadSections:withRowAnimation:
方法时,UITableView会校验其他section,如果发现UITableView内记录的某section的row的数量和[dataSource tableView:numberOfRowsInSection]
返回的不一致时,抛出NSInternalInconsistencyException
异常。
崩溃案例
其实reloadSections引起崩溃的原因非常简单。但是虽然简单,还是很容易在不经意间引起崩溃。那么继续来看下具体的案例,加深下印象。
- 案例一:延迟reload场景。
出于业务的某些需要,当SectionOne的数据源个数变化时,延迟刷新TableView。
- (void)onSectionOneUpdate{
[self performSelector:@selector(reloadSectionOne) withObject:nil afterDelay:0.1f];
}
- (void)reloadSectionOne{
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
}
那么在这0.1秒当中,对其他section进行reload则会引发崩溃。
- (void)reloadSectionTwo{
// It will crash.
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
}
崩溃的原因自然是因为SectionOne的数据源个数和UITableView中的不一致导致。要解决这个场景的崩溃其实也很简单。用一个NSMutableIndexSet
变量记录需要reload的section。
- (void)onSectionOneUpdate{
[self.sectionNeedReload addIndex:0];
// delay reload
[self performSelector:@selector(reloadSections:) withObject:nil afterDelay:0.1f];
}
- (void)onSectionTwoUpdate{
[self.sectionNeedReload addIndex:1];
[self reloadPartSection];
}
- (void)reloadSections{
if ([self.sectionNeedReload count]){
[self.tableView reloadSections:self.sectionNeedReload withRowAnimation:UITableViewRowAnimationAutomatic];
[self.sectionNeedReload removeAllIndexes];
}
}
- 案例二:Section的numberOfRow依赖于其他Section
UITableView有两个Section。整个UITableView都没有数据时,需要在section0中显示一个特殊的EmptyCell
,提示用户当前UITableView没有数据。那么先看下[dataSource tableView:numberOfRowsInSection:]
的实现。
// dataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (section == 0){
if ([self dataCountInSection:0] == 0 && [self dataCountInSection:1] == 0){
return 1;
}
}
return [self dataCountInSection:section];
}
那么当程序按照以下步骤运行时,就必然崩溃。
- UITableView没有数据。section0中有一个EmptyCell。
- secton1数据源增加一个item
- 调用reloadSections,只刷新section1。程序崩溃。
section1数据源增加item时,其实也影响到了section0。单纯刷新section1就会崩溃了。
对于这种场景,简单的做法是特别处理item个数由0增加至1的情况,调用reloadData进行刷新。但是我个人认为,EmptyCell不应该由这种方式来实现。使用UITableView时,需要保证数据源item和UITableViewCell一一对应。凭空捏造一个EmptyCell不好维护。容易导致NSInternalInconsistencyException
。