UIManagedDocument с NSFetchedResultsController и фоновым контекстом

Я пытаюсь получить следующую работу.

У меня есть табличное представление, которое отображает данные, полученные из API, в табличном представлении. Для этой цели я использую NSFetchedResultsController:

self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                        managedObjectContext:self.database.managedObjectContext
                                                                      sectionNameKeyPath:nil
                                                                               cacheName:nil];

Я создаю свои объекты в фоновом контексте следующим образом:

    NSManagedObjectContext *backgroundContext;
    backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    backgroundContext.parentContext = document.managedObjectContext; 

    [backgroundContext performBlock:^{
        [MyAPI createEntitiesInContext:backgroundContext];

        NSError *error = nil;
        [backgroundContext save:&error];
        if (error) NSLog(@"error: %@",error.localizedDescription);

        [document.managedObjectContext performBlock:^{
            [document updateChangeCount:UIDocumentChangeDone];
            [document.managedObjectContext save:nil];
        }];

Теперь, когда я получаю новые данные (и вставляю/обновляю объекты, как показано чуть выше), мой NSFetchedResultsController работает не так, как должен. В частности, я всегда обновляю один объект (а не создаю новый), но в табличном представлении отображаются два объекта. Как только я перезапускаю приложение, оно отображается правильно.

Если я выполняю создание объектов ([MyAPI createEntities]) в self.database.managedObjectContext, все работает нормально.

Любая идея, что я делаю неправильно? Просмотр существующих тем здесь, на SO, заставляет меня думать, что я делаю это правильно. Опять же, если я не сохраняю основные данные в фоновом контексте (но в document.managedObjectContext), то все работает нормально...


person user1013725    schedule 05.07.2012    source источник


Ответы (3)


Сегодня я прочитал о похожей проблеме на форумах разработчиков Apple. Возможно, это та же проблема, что и у вас, https://devforums.apple.com/message/666492#666492, и в этом случае, возможно, есть ошибка (или, по крайней мере, кто-то еще с такой же проблемой, чтобы обсудить ее!).

Предполагая, что это не так, похоже, что то, что вы хотите сделать, должно быть вполне возможно с вложенными контекстами и, следовательно, при условии отсутствия ошибок с UIManagedDocument.

Моя единственная оговорка заключается в том, что я пытался заставить пакетную загрузку работать с UIManagedDocument, и похоже, что она не работает с вложенными контекстами (https://stackoverflow.com/q/11274412/1347502). Я думаю, что одним из основных преимуществ NSFetchedResultsController является его способность повышать производительность за счет пакетной загрузки. Так что, если это невозможно сделать в UIManagedDocument, возможно, NSFetchedResultsController не готово для использования с UIManagedDocument, но я еще не дошел до сути этой проблемы.

Помимо этой оговорки, большая часть инструкций, которые я читал или просматривал о вложенных контекстах и ​​фоновой работе, похоже, выполнялись с равноправными дочерними контекстами. То, что вы описали, - это родительская, дочерняя, внучатая конфигурация. В видео WWDC 2012 «Session 214 — Core Data Best Practices» (+ 16:00 минут) Apple рекомендует добавить еще один одноранговый контекст в родительский контекст для этого сценария, например

backgroundContext.parentContext = document.managedObjectContext.parentContext;

В этом контексте работа выполняется асинхронно, а затем передается родительскому объекту с помощью вызова сохранения в фоновом контексте. Затем родитель будет сохранен асинхронно, и любые одноранговые контексты, в данном случае document.managedObjectContext, получат доступ к изменениям через выборку, слияние или обновление. Это также описано в документации UIManagedDocument:

  • При необходимости вы можете загружать данные из фонового потока непосредственно в родительский контекст. Вы можете получить родительский контекст, используя parentContext. Загрузка данных в родительский контекст означает, что вы не нарушаете операции дочернего контекста. Вы можете получить данные, загруженные в фоновом режиме, просто выполнив выборку.

[Редактировать: перечитывая это, вы можете просто порекомендовать предложение Джеффри, то есть вообще не создавать никаких новых контекстов, а просто использовать родительский контекст.]

При этом документация также предполагает, что обычно вы не вызываете сохранение в дочерних контекстах, а используете методы сохранения UIManagedDocument. Это может быть случай, когда вы вызываете save или, возможно, часть проблемы. Вызов сохранения в родительском контексте настоятельно не рекомендуется, как упоминал Джеффри. Другой ответ, который я прочитал о переполнении стека, рекомендовал использовать только updateChangeCount для запуска сохранения UIManagedDocument. Но я ничего не читал от Apple, поэтому, возможно, в этом случае будет уместно вызвать метод UIManagedDocument saveToURL:forSaveOperation:completionHandler:, чтобы все синхронизировать и сохранить.

Я предполагаю, что следующая очевидная проблема заключается в том, как уведомить NSFetchedResultsController о произошедших изменениях. У меня возникло бы искушение упростить настройку, как обсуждалось выше, а затем подписаться на различные NSManagedObjectContextObjectsDidChangeNotification или сохранить уведомления в разных контекстах и ​​посмотреть, какие, если таковые имеются, вызываются, когда UIMangedDocument сохраняет, автосохраняет или когда фоновые изменения сохраняются в родительском ( допустимо в данном случае). Я предполагаю, что NSFetchedResultsController подключен к этим уведомлениям, чтобы синхронизироваться с базовыми данными.

В качестве альтернативы, возможно, вам нужно вручную выполнить выборку, слияние или обновление в основном контексте, чтобы получить изменения, а затем каким-то образом уведомить NSFetchedResultsController о необходимости обновления?

Лично мне интересно, готово ли UIManagedDocument для общего пользования, на WWDC в этом году о нем не упоминалось, и вместо этого было представлено продолжительное обсуждение того, как создать гораздо более сложное решение: «Сессия 227 — Использование iCloud с Core Data»

person Rory O'Bryan    schedule 06.07.2012

В моем методе, где я извлекаю данные с сервера, я сначала создаю объекты, а затем вызываю эти два метода для сохранения изменений в документе:

[self.managedObjectContext performBlock:^{
     // create my entities


     [self.document updateChangeCount:UIDocumentChangeDone];
     [self.document savePresentedItemChangesWithCompletionHandler:^(NSError *errorOrNil) {
            ...
      }];
}];
person carmen_munich    schedule 26.11.2013

Поскольку вы обновляете результаты в другом контексте, я думаю, вам нужно будет вызвать [self.fetchedResultsController performFetch:&error] в методе -viewWillAppear: вашего контроллера представления.


После обновлений

Хорошо, вы не должны звонить [backgroundContext save:&error] или [document.managedObjectContext save:nil]. См.: Справочник по классу UIManagedDocument.

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

Мне пришлось использовать -insertedObjects и obtainPermanentIDsForObjects:error: для сохранения новых объектов, созданных в контексте.

Далее, я не думаю, что вам нужно создавать новый контекст для работы в фоновом режиме. document.managedObjectContext.parentContext должен быть доступным фоновым контекстом для запуска обновлений.

Наконец, я не очень часто звоню [document updateChangeCount:UIDocumentChangeDone]. Об этом позаботится документ автоматически. Вы все еще можете сделать это в любое время, когда захотите, но в этом нет необходимости.

Вот как я бы назвал Ваш метод -createEntitiesInContext.

[document.managedObjectContext.parentContext performBlock:^{
    [MyAPI createEntitiesInContext:document.managedObjectContext.parentContext];

    NSSet *objects = [document.managedObjectContext.parentContext insertedObjects];
    if (objects.count > 0) {
        NSError *error = nil;
        [document.managedObjectContext.parentContext obtainPermanentIDsForObjects:objects error:&error]
        if (error) NSLog(@"error: %@",error.localizedDescription);
    }
}];
person Jeffery Thomas    schedule 05.07.2012
comment
Хм, я думал, что изменения в разных контекстах будут объединены, поэтому fetchedResultsController в конечном итоге получит уведомление. Во всяком случае, я попытался вызвать [self.fetchedResultsController PerformFetch: nil] после сохранения backgroundContext, но все та же ситуация: в моем табличном представлении отображаются два объекта, хотя должен быть только один. После перезапуска приложение работает нормально. - person user1013725; 06.07.2012
comment
Также помните, что у меня есть UIDocument, может быть, это что-то меняет? Я в основном хочу сохранить/обновить новые объекты в моем документе в фоновом потоке. Если я выполняю сохранение управляемого объекта UIDocumentContext, пользовательский интерфейс отстает. Вот почему я попытался сделать это с помощью backgroundContext, но почему-то все запуталось/не слилось/без понятия, что происходит. Я уже совсем отчаялся. - person user1013725; 06.07.2012
comment
К сожалению, ваш код все еще не работает для меня. Это дает мне совершенно странные и непредсказуемые результаты. Для меня работало использование фонового контекста, а затем вызов сохранения и получение постоянных идентификаторов для этого фонового контекста. Однако теперь я сталкиваюсь с проблемой условий гонки, если этот код обновления вызывается дважды одновременно, я думаю, это потому, что он создает сущности в двух разных контекстах, а затем объединяет их. Не уверен, как это предотвратить. - person user1013725; 11.07.2012
comment
@ user1013725 Извините, но я думаю, что вы зашли в тупик. Apple не поддерживает контекст -save: при использовании управляемого документа. Возможно, после получения постоянных идентификаторов вам следует позвонить [document updateChangeCount:UIDocumentChangeDone] - person Jeffery Thomas; 11.07.2012