0%

UITableView reloadSections引发的崩溃

在iOS的开发中,UITableView是最经常用到的UI组件之一。然而,UITableView却非常容易引起「NSInternalInconsistencyException(数据不一致)」的崩溃。其中,调用reloadSections时,一不留神就会引发崩溃。

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。
1
2
3
4
5
6
7
8
9
- (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则会引发崩溃。

1
2
3
4
5
- (void)reloadSectionTwo
{
// It will crash.
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
}

崩溃的原因自然是因为SectionOne的数据源个数和UITableView中的不一致导致。要解决这个场景的崩溃其实也很简单。用一个NSMutableIndexSet变量记录需要reload的section。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (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:]的实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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];
}

那么当程序按照以下步骤运行时,就必然崩溃。

  1. UITableView没有数据。section0中有一个EmptyCell。
  2. secton1数据源增加一个item
  3. 调用reloadSections,只刷新section1。程序崩溃。

section1数据源增加item时,其实也影响到了section0。单纯刷新section1就会崩溃了。

对于这种场景,简单的做法是特别处理item个数由0增加至1的情况,调用reloadData进行刷新。但是我个人认为,EmptyCell不应该由这种方式来实现。使用UITableView时,需要保证数据源item和UITableViewCell一一对应。凭空捏造一个EmptyCell不好维护。容易导致NSInternalInconsistencyException