У меня есть UITableView, показывающий объекты из объекта Core Data, полученные от WebServer, через NSFetchResultController
. Пользователь может изменить их и отправить обратно на сервер. Она также может нажать кнопку, чтобы обновить эти объекты с сервера.NSFetchResultsController показывает ложные объекты
Каждый объект имеет атрибут идентификатора. Когда я получаю объект JSON с сервера, я ищу существующий объект с тем же идентификатором. Он существует, я обновляю его. В противном случае я его создам.
Некоторые из этих событий происходят с главной очередью NSManagedObjectContext
, некоторые из них в дочернем режиме Private Queue one. Во всех случаях это происходит в методе performBlock
, и как дочерний контекст, так и его родительский объект сохраняются.
Это звучит как хлеб и масло. Теперь моя проблема:
Иногда, после обновления сервера, NSFetchResultController
показывает два экземпляра одного и того же объекта. Две копии различны (их указатели разные). Одна копия завершена, другая имеет свои значения атрибутов, а не ее отношения. Оба имеют одинаковые NSManagedObjectContext
. Оба имеют одинаковый идентификатор.
Как я могу отладить такую проблему? Я проверил, что у моего хранилища CoreData нет есть два экземпляра одного и того же объекта (путем поиска внутри SQLite-файла, а также путем размещения символьной точки останова на awakeFromInsert
). Я проследил код, который ищет существующий экземпляр, и он находит это в порядке.
На данный момент я застреваю, и мне сложно представить стратегию отладки.
Я могу предоставить все детали, которые только можно вообразить, но помимо полного отображения исходного кода я не уверен, что было бы самым полезным.
Спасибо за любую помощь.
JD
Edit 1: Вот мой controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:(DaySlotCell*)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
Edit 2: вот как мои контексты устроены. У меня есть центральный объект модели singleton, который заботится о связи с удаленным сервером (таким образом, его имя класса SGIServer
). Она имеет два контекста:
mainManagedObjectContext
,NSMainQueueConcurrencyType
, используется для всех UI-связанных вещей, включенных вNSFetchResultController
описанный выше (хотя я прочитал в интернете, чтоNSFetchResultController
может использовать частный контекст). Он не связан с постоянным хранилищем.Это ребенок:persistentManagedObjectContext
, вNSPrivateQueueConcurrencyType
, связанный с постоянной памятью, отвечающими за сохранение в магазин в фоновом режиме:
Они создаются во время запуска, как это:
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator;
self.persistentManagedObjectContext = managedObjectContext;
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
managedObjectContext.parentContext = self.persistentManagedObjectContext;
self.mainManagedObjectContext = managedObjectContext;
код, который необходим контекст сделать это в два различных способа в зависимости от того, что они хотят основной контекст или нет:
NSManagedObjectContext *moc = [server mainManagedObjectContext];
или
NSManagedObjectContext *moc = [server newPrivateContext];
где newPrivateContext
просто создает новый NSPrivateQueueConcurrencyType
контекст, ребенок главного одного:
- (NSManagedObjectContext *) newPrivateContext
{
NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
privateContext.parentContext = self.mainManagedObjectContext;
return privateContext;
}
Наконец, я определил два save
методы, синхронное один и асинхронный один:
- (void)syncSaveContext: (NSManagedObjectContext *) moc persisting:(BOOL)saveToDisk
{
NSManagedObjectContext *mainContext = self.mainManagedObjectContext;
if (moc && moc != mainContext) {
NSError *error = nil;
if (![moc save:&error]) {
NSLog(@"Error saving MOC: %@\n%@",[error localizedDescription], [error userInfo]);
}
}
if (mainContext && [mainContext hasChanges]) {
[mainContext performBlockAndWait:^{
NSError *error = nil;
if (![mainContext save:&error]) {
NSLog(@"Error saving MOC: %@\n%@",[error localizedDescription], [error userInfo]);
}
}];
}
if (saveToDisk) {
NSManagedObjectContext *privateContext = self.persistentManagedObjectContext;
if (privateContext && [privateContext hasChanges]) {
[privateContext performBlockAndWait: ^{
NSError *error = nil;
if (![privateContext save:&error]) {
NSLog(@"Error saving private MOC: %@\n%@",[error localizedDescription], [error userInfo]);
}
}];
}
}
}
и:
- (void)asyncSaveContext: (NSManagedObjectContext *) moc persisting:(BOOL)saveToDisk
{
NSManagedObjectContext *mainContext = self.mainManagedObjectContext;
if (moc && moc != mainContext) {
NSError *error = nil;
if (![moc save:&error]) {
NSLog(@"Error saving MOC: %@\n%@",[error localizedDescription], [error userInfo]);
}
}
if (mainContext && [mainContext hasChanges]) {
[mainContext performBlock:^{
NSError *error = nil;
if ([mainContext save:&error]) {
if (saveToDisk) {
NSManagedObjectContext *privateContext = self.persistentManagedObjectContext;
if (privateContext && [privateContext hasChanges]) {
[privateContext performBlock: ^{
NSError *error = nil;
if (![privateContext save:&error]) {
NSLog(@"Error saving private MOC: %@\n%@",[error localizedDescription], [error userInfo]);
}
}];
}
}
} else {
NSLog(@"Error saving MOC: %@\n%@",[error localizedDescription], [error userInfo]);
}
}];
}
}
Асинхронный один из них наиболее часто используется, как правило, в конце любого действия, инициированного пользователем. Синхронизация используется иногда, когда я хочу убедиться, что сохранение было выполнено до того, как я продолжу.
у вас есть код для '' (пустоты) controller: (NSFetchedResultsController *) controller didChangeObject: (id) anObject atIndexPath: (NSIndexPath *) indexPath forChangeType: (NSFetchedResultsChangeType) type newIndexPath: (NSIndexPath *) newIndexPath''? дайте ему – user996142
да, самый распространенный. См. Мое редактирование –
Вы пытались отладить с точкой останова в '' NSFetchedResultsChangeInsert''? – user996142