一個友盟BUG的思考和分析:Invalid update


1.友盟錯誤信息

Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (11)
 must be equal to the number of rows contained in that section before the update (11), plus or minus the number of rows inserted or deleted 
from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
(null)

2.錯誤信息解析

上面的錯誤信息,大意是:調用insertRowsAtIndexPaths或deleteRowsAtIndexPaths時,表格 的 行數 一定要與數據源的數量一致。

2.1原因分析1:異步線程更新數據源

下面使用一個Demo來復現這種情況:

- (void)p_addRow {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:0.5];  //模擬網絡數據加載
        [self.arrData addObject:@"cell number 1 --"];
        
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView beginUpdates];
            [self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.arrData.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
            [self.tableView endUpdates];
        });
    });
}

- (void)p_deleteRow {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:0.5];  //模擬網絡數據加載
        [self.arrData removeObjectAtIndex:0];
        
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView beginUpdates];
            [self.tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.arrData.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
            [self.tableView endUpdates];
        });
    });
}

這兩個方法調用的時候,我們先按照常規的操作調用,每點擊一次,添加/刪除一條數據,這種情況下,從實際效果可以看到不會出現崩潰的現象。

現在用一個定時器來模擬手點的效果,經過試驗得知,當定時器執行的時間間隔過快,而網絡數據反應太慢的情況,會出現崩潰的現象,代碼如下:

- (void)leftButton {
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(p_deleteRow) userInfo:nil repeats:YES];
}

- (void)rightButton {
    [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(p_addRow) userInfo:nil repeats:NO];
}

- (void)p_addRow {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:0.5];  //模擬網絡數據加載
        [self.arrData addObject:@"cell number 1 --"];
        
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView beginUpdates];
            [self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.arrData.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
            [self.tableView endUpdates];
        });
    });
}

- (void)p_deleteRow {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:0.5];  //模擬網絡數據加載
        [self.arrData removeObjectAtIndex:0];
        
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView beginUpdates];
            [self.tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.arrData.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
            [self.tableView endUpdates];
        });
    });
}

崩潰日志如下:

這樣看還是有點抽象,我們加一個變量來保存當前表格的行數以及在執行insertRowsAtIndexPaths時數據源的表格行數:

從結果可以看到,由於獲取數據是在異步線程里面進行的,導致在執行insertRowsAtIndexPaths時,實際上數據源已經進行了多次插入操作,這樣,只執行一次insertRowsAtIndexPaths,便會出現崩潰現象了。

2.2原因分析2:主線程更新數據源

上面小節是在異步線程中更新數據源,這里我們把數據源的放在主線程,看看實際效果:

使用上面的代碼,我們可以看到沒有出現崩潰的問題,那么是不是表示在主線程更新數據源就沒有問題呢?我們試一下這種情況:如果通過網絡加載或其他方式加載數據的時候,數據源有多條數據呢?還是看代碼演示:

從這里可以看到,如果數據源更新了多條數據,仍然在insertRowsAtIndexPaths方法里面只加了一條數據時,便會出現崩潰現象。

2.3其他情況

上兩節的內容分析的崩潰原因,有一個共同點:下面這兩個數字是不一樣的(刪除的話,前面那個數字應該小於后面那個數字,如果不是這個規則,應該是網絡又更新了數據源)。

The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (0)

但從UMeng的崩潰日志看,實際上還有一種情況,那就是這兩個數字是一樣的。這種情況是怎么發生的呢?我們修改一下代碼:

從代碼可以看到:更新數據源之后,先調用了一下[self.tableView reloadData],這個時候因為表格數據源已經更新,並且表格也已經更新,這個時候再調用一次insertRowsAtIndexPaths方法,就會接着導致數據源和表格行不一致,從而導致崩潰。

3.解決方案

通過對我們工程代碼的分析,數據源的操作是在主線程(對涉及到數據源的地方,都打印了線程的日志信息,顯示當前線程為主線程),因此初步判斷出現崩潰的原因為2.2小節和2.3小節所描述的。這兩種情況,我們先用Demo來演示一下解決方案看看是不是有效的:

逐步放開兩個注釋內容,可以看到,這三個條件下,都不會崩潰。

同樣的,演示一下刪除:

逐步放開兩個注釋內容,可以看到,這三個條件下,也不會崩潰。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM