1. 程式人生 > >UITableView——reloadData與reloadSection效能比較

UITableView——reloadData與reloadSection效能比較

週五上午,測試,有bug:每次reset模擬器後,第一次進入介面,閃退,第二次進入介面,結果正常。

以下是這個bug的錯誤日誌:

 *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 1.  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 (2), 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).’

發現重新整理列表時,閃退。問題出在:[self.tableViewreloadSections:[NSIndexSetindexSetWithIndex:2]withRowAnimation:UITableViewRowAnimationNone];

這個方法我沒用過,從方法名看出,這個方法是區域性重新整理列表的某個section。但一般我都是用reloadData,重新整理整個列表。於是,我把reloadSection改回reloadData,發現這個兩個bug都不再出現了。

按道理,使用reloadSection應該沒有問題啊,但為什麼這裡不能使用reloadSection呢?

其實,仔細看錯誤日誌就明白,問題出在該section的row是動態變化的。

那麼,問題來了,為什麼要把reloadData改成reloadSection呢?

應該是因為reloadSection效率更高,速度更快?

但真的是這樣嗎?我想驗證一下。

好吧,我承認我是有多無聊?閒的蛋疼……

新建一個test工程,建立一個tableview,3個section,每個section有30行。這個tableview總共有90行資料。

程式碼如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tableview.delegate = self;
    self.tableview.dataSource = self;
    
//    NSLog(@"start");
//    for(int i=0;i<1000000;i++){
//        [self.tableview reloadSections:[NSIndexSet indexSetWithIndex:2] withRowAnimation:UITableViewRowAnimationNone];
//    }
//    NSLog(@"end");
    
    NSLog(@"start");
    for(int i=0;i<1000000;i++){
        [self.tableview reloadData];
    }
    NSLog(@"end");
    
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return 3;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return 30;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"cell"];
    cell.textLabel.text = [NSString stringWithFormat:@"section:%li,row:%li",(long)indexPath.section,(long)indexPath.row];
    for(int i=0;i<1000;i++){
        int y = indexPath.row+indexPath.section;
    }
    UIImageView *image = [[UIImageView alloc] initWithFrame:CGRectMake(200, 5, 90, 75)];
    image.image = [UIImage imageNamed:@"254"];
    [cell addSubview:image];
    return cell;
}

在iPhone 6 plus模擬器下做了三組對照試驗,試驗結果如下:

在每個cell只有一個label的情況下,分別用reloadSection,reloadData方法重新整理列表,10次,100次,1000次,10000次,100000次,1000000次時,時間、CPU、記憶體的比較:


每個cell有一個label和一張圖片,比較


每個cell有一個label、一張圖片和執行1000次的for迴圈,比較


每個cell有一個label、一張圖片和執行1000次的for迴圈,reloadSection,reloadData,在執行10次~1000000次花費時間對數表(前兩種情況就不列圖表了,和最後情況類似)


reloadSection,reloadData,在執行10次~1000000次花費記憶體對數表


從這些實驗資料發現:在10000次以內,reloadSection和reloadData兩者在時間、CPU、記憶體相差並不大,甚至在某些情況,reloadData效能要優於reloadSection。大於10000次以後,reloadSection的效能高於reloadData。

而我們實際的專案中幾乎不會重新整理某個列表超過100次,兩者效能差不多,但reloadSection不能用於row,section動態變化的情況下,所以還是更加推薦使用reloadData方法

總結:平時編碼過程中,通常會根據自己的經驗判斷,採用某種價效比更高的方式。但事實真的是這樣的嗎?我們很少去想這個問題,也幾乎不會去驗證,因為我們覺得理所當然。套用知乎的一句名言:凡事先問是不是,再問為什麼,警戒自己,不要被自己所謂的“經驗”誤導。

———————————————————————我是分割線———————————————————————————

之後有同事提到:

關於reloadSections vs reloadData

測試例子考慮的是資料來源不變的情況下cell的重繪製

但複雜場景,之所以考慮部分重新整理reloadSections,是因為重新整理每個section,cell不單單是cell的繪製,也都有包含I/O和運算操作

而這個時候 reloadData會觸發其他不必要的運算和I/O


tableview的主要作用是展示資料,而I/O操作其實不適合放在cell中。

包括圖片的下載其實不宜放在cell中,這會導致介面卡頓。

更好的方法是:將獲取資料來源的程式碼放到次要執行緒中,這樣主執行緒才能更好的載入檢視。

我們常用的MVC,MVVM架構也就是為了將業務邏輯與檢視儘量剝離,讓UI更好的展示資料。

至於運算操作,在第三種情況中,我加了一個執行一千次的for迴圈來模擬複雜的運算。

reloadData相比reloadSection,前者執行運算操作的次數是後者的三倍。

在這種情況下,兩者的效能在100000次以內,依然相差無幾。

而在資料來源動態變化的情況下,例如在某些頁面中,每個Section中的row是動態變化的,單獨使用reloadSection會導致文中的bug。而使用reloadData不會出現這個bug。

而reloadSection相比reloadData多一點的是,在重新整理的時候有動畫。

更多情況下,是搭配beginUpdates和endUpdates來實現 deleteSections:withRowAnimation:的功能。