閱讀293 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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

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

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

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

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

所以梳理了下邏輯:

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

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

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

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

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

<span >- (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 >    Player *playerObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];
    playerObject.name = [NSString stringWithFormat:@"name-%d", arc4random() % 10000];</span>

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

<span >- (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


最後更新:2017-04-03 15:22:09

  上一篇:go 11384 - Help is needed for Dexter 模擬 98
  下一篇:go URAL 1132 二次剩餘