1. 程式人生 > >iOS-reloadSections(UITableView )引發的崩潰

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];
}

那麼當程式按照以下步驟執行時,就必然崩潰。

  1. UITableView沒有資料。section0中有一個EmptyCell。
  2. secton1資料來源增加一個item
  3. 呼叫reloadSections,只重新整理section1。程式崩潰。

section1資料來源增加item時,其實也影響到了section0。單純重新整理section1就會崩潰了。

對於這種場景,簡單的做法是特別處理item個數由0增加至1的情況,呼叫reloadData進行重新整理。但是我個人認為,EmptyCell不應該由這種方式來實現。使用UITableView時,需要保證資料來源item和UITableViewCell一一對應。憑空捏造一個EmptyCell不好維護。容易導致NSInternalInconsistencyException