4

У меня есть 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]); 
      } 
     }]; 
    } 
} 

Асинхронный один из них наиболее часто используется, как правило, в конце любого действия, инициированного пользователем. Синхронизация используется иногда, когда я хочу убедиться, что сохранение было выполнено до того, как я продолжу.

+0

у вас есть код для '' (пустоты) controller: (NSFetchedResultsController *) controller didChangeObject: (id) anObject atIndexPath: (NSIndexPath *) indexPath forChangeType: (NSFetchedResultsChangeType) type newIndexPath: (NSIndexPath *) newIndexPath''? дайте ему – user996142

+0

да, самый распространенный. См. Мое редактирование –

+0

Вы пытались отладить с точкой останова в '' NSFetchedResultsChangeInsert''? – user996142

ответ

3

Если у вас есть какой-то два контекста, настроенных (родительский и дочерний, главная очередь и приватная очередь), и это, как правило, происходит после вызова сохранения в ваших контекстах, тогда у вас может быть аналогичная проблема с тем, что у меня было, где временные объекты просачиваются в ваш контекст. Насколько я знаю, это ошибка в основных данных

Где вы вызываете сохранить, попробуй вызвать obtainPermanentIDsForObjects внутри родительского контекста выполнение блока, как это:

[self.parentContext performBlockAndWait:^{ 
      NSError * error = nil; 
      [self.parentContext obtainPermanentIDsForObjects:[self.parentContext.insertedObjects allObjects] error:&error]; 
      [self.parentContext save: &error] 
     }]; 
+0

Спасибо. Я попытаюсь реализовать предложение и вернуться с обратной связью. –

+1

Вот и все. Это простое изменение, прежде чем я сохраню какой-либо контекст (таким образом, в моих двух методах сохранения, указанных в моем вопросе), устранено любое появление ложных линий. Я даже не уверен, что они произошли при спасении (поскольку я экономлю часто и рано), так что это немного похоже на вуду, но я не мог воспроизвести проблему сейчас. Большое спасибо –

+0

Да, это ужасная проблема. Я провел пару дней, проследив за ним, пока не нашел отчет об ошибке в каком-то неясном месте. Рад, что это сработало! – BinaryGuy

Смежные вопросы