iOS-reloadSections(UITableView )引發的崩潰
在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。
- (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
。