Core Data多線程環境下pendingChange引發的排序不對問題

這是一個起初看起來很神奇的問題,大意如下:

  • 有一個Table,展示多個消息會話,這些消息會話按最新消息時間排序;
  • 某種情況下,新收到一條消息,時間展示爲最新,但這條消息沒有排在最上方。

因爲界面上展示的時間是最新的,所以剛開始遇到這個問題的第一反應是看看數據庫裏面的時間戳是不是正確的,查看後確認時間是最新的沒錯。

一時陷入了僵局,因爲問題很難重現。

所以梳理了下邏輯:

  1. 收到新消息,在後臺進行處理,執行save動作;
  2. Core Data保存後發出消息通知變更,主線程使用NSFetchedResultsController和UITableView綁定,收到消息後刷新界面;
  3. UI界面根據dataSource進行展現,而dataSource根據latestTime進行排序;

因爲無法重現,所以先加上了日誌輸出信息,觀察出了發生該現象的時候,主線程都收到兩次刷新通知,正常情況下沒有。

主線程爲什麼會發生兩次刷新通知呢?

  • 主線程內存上發生了變動;
  • 其它線程對持久化層做了寫動作,通知到主線程。

所以我就在想主線程在內存上發生了什麼變動,找了很久但是沒找到什麼東西。後來同事一語道破天機,打印出changeValues:

<span style="font-size:14px">- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    NSLog(@"didChangeObject %@ from %@ to %@ with %d type, \n change value : %@\n", anObject, indexPath, newIndexPath, type, [anObject changedValues]);
}</span>

通過這樣的日誌信息可以發現主線程在內存中發生了什麼變化。

爲了驗證問題是不是這樣引發的,我在一個Demo上進行了模擬和驗證(這個Demo是之前一篇博文使用的):

我通過在主線程修改Core Data對象的值(不一定要sortKey),但不保存:

<span style="font-size:14px">    Player *playerObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];
    playerObject.name = [NSString stringWithFormat:@"name-%d", arc4random() % 10000];</span>

接着在其它線程修改sortKey,引發主線程進行刷新:

<span style="font-size:14px">- (void)changeSortKeyInOtherContext:(NSManagedObjectID *)objectId
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
        
        NSPersistentStoreCoordinator *sharedPersistentStoreCoordinator = self.cdViewController.persistentStoreCoordinator;
        [tmpContext setPersistentStoreCoordinator:sharedPersistentStoreCoordinator];
        
        Player *playerObject = (Player *)[tmpContext objectWithID:objectId];
        
        int age = arc4random() % 100;
        playerObject.age = @(age);
        
        int salary = arc4random() % 10000000;
        playerObject.salary = @(salary);
        
        NSError *error = NULL;
        if (tmpContext && [tmpContext hasChanges] && ![tmpContext save:&error]) {
            NSLog(@"Error %@, %@", error, [error localizedDescription]);
            abort();
        }
        
        [tmpContext release], tmpContext = nil;
    });
}</span>

這樣就可以模擬出問題場景,進而得到驗證。

—— Jason Lee @ Hangzhou


發佈了194 篇原創文章 · 獲贊 728 · 訪問量 173萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章