這是一個起初看起來很神奇的問題,大意如下:
有一個Table,展示多個消息會話,這些消息會話按最新消息時間排序;
某種情況下,新收到一條消息,時間展示為最新,但這條消息沒有排在最上方。
因為界面上展示的時間是最新的,所以剛開始遇到這個問題的第一反應是看看數據庫裡面的時間戳是不是正確的,查看後確認時間是最新的沒錯。
一時陷入了僵局,因為問題很難重現。
所以梳理了下邏輯:
收到新消息,在後台進行處理,執行save動作;
Core Data保存後發出消息通知變更,主線程使用NSFetchedResultsController和UITableView綁定,收到消息後刷新界面;
UI界面根據dataSource進行展現,而dataSource根據latestTime進行排序;
因為無法重現,所以先加上了日志輸出信息,觀察出了發生該現象的時候,主線程都收到兩次刷新通知,正常情況下沒有。
主線程為什麼會發生兩次刷新通知呢?
主線程內存上發生了變動;
其它線程對持久化層做了寫動作,通知到主線程。
所以我就在想主線程在內存上發生了什麼變動,找了很久但是沒找到什麼東西。後來同事一語道破天機,打印出changeValues:
[cpp]
<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),但不保存:
[cpp]
<span style="font-size:14px"> Player *playerObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];
playerObject.name = [NSString stringWithFormat:@"name-%d", arc4random() % 10000];</span>
接著在其它線程修改sortKey,引發主線程進行刷新:
[cpp]
<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