NSFetchedResultsController не удается получить в дочернем контексте

У меня есть UIManagedDocument с некоторыми данными, которые я отображаю в списке, используя NSFetchedResultsController. Данные регулярно обновляются в фоновом режиме, и изменения помещаются в UIManagedDocument.managedObjectContext (используя PerformBlock:).

Когда я отображаю данные из основного контекста документа, все работает как положено. Но как только я отображаю список в контексте, который является дочерним по отношению к основному контексту (child.parentContext = document.managedObjectContext), я не вижу никаких объектов, и в консоли выводится следующая ошибка:

foo[17895:15203] CoreData: error: (NSFetchedResultsController)
                 The fetched object at index 5 has an out of order section name 'E.
                 Objects must be sorted by section name'

Это происходит только после того, как новый объект был вставлен в контакт документов. Когда я достаточно долго жду автоматического сохранения, список отображается нормально. Также проблема возникает только тогда, когда у меня установлен sectionNameKeyPath на NSFetchedResultsController и только с дочерним контекстом.

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

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Contact"];
fetchRequest.sortDescriptors = [Contact userDefinedSortDescriptors];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"hidden == nil || hidden == NO"];

NSFetchedResultsController *fetchedResultsController = 
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                    managedObjectContext:_managedObjectContext
                                      sectionNameKeyPath:[Contact userDefinedSectionNameKeyPath]
                                               cacheName:@"ContactList"];

[Contact userDefinedSortDescriptors] и [Contact userDefinedSectionNameKeyPath] разрешаются во время выполнения. Дескрипторы сортировки содержат в качестве первой записи sectionNameKeyPath. Также не может быть nil или других забавных вещей.

Изменить: уточнены некоторые неясные моменты. В частности, я не вызываю -save: в контексте управляемого объекта документов.

Редактировать 2: я попытаюсь объяснить, как MOC связаны друг с другом.

В игре есть три контекста управляемых объектов:

1) UIManagedDocument.managedObjectContext создается путем загрузки документа.

2) Работает фоновый поток, который время от времени обновляет объекты. Это moc частной очереди с документами MOC в качестве родителя:

context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.parentContext = repository.managedObjectContext;

[context performBlock:^{ /* updates */ }];
[context performBlock:^{ [context save:NULL]; }];

3) Когда пользователь хочет внести изменения, создается новый MOC как дочерний элемент MOC документа. Это MOC основной очереди. Это контекст, который используется для выполнения показанной выше выборки.

Фоновые обновления выполняются из NSOperationQueue, но весь доступ к фоновому MOC правильно окружен -performBlock:. Все остальные обращения выполняются из основного потока.

Редактировать 3. Играя с некоторыми настройками на NSFetchRequest, я обнаружил, что проблема исчезает при настройке fetchRequest.includesPendingChanges = NO. Но это нежизнеспособное решение, потому что теперь пользователь больше не видит никаких обновлений, пока изменения не будут сохранены в фоновом режиме с помощью UIManagedDocument.


person Alfonso    schedule 17.04.2012    source источник
comment
Множественные контексты сложны. Итак, ваш _managedObjectContext является дочерним элементом основного MOC документа? В какой контекст вы вставляете свои объекты? Кроме того, взгляните на setShouldRefreshRefetchedObjects и другие свойства выборки, которые определяют, как выборка на самом деле получает свои объекты.   -  person Jody Hagins    schedule 18.04.2012
comment
_managedObjectContext является либо основным MOC, либо дочерним по отношению к основному MOC, в зависимости от контекста загрузки контроллера. Когда это основной MOC, то все в порядке, когда это ребенок, у меня возникает проблема, которую я описал. Я вставляю свои объекты в другой дочерний MOC и сохраняю этот дочерний элемент, когда закончу.   -  person Alfonso    schedule 18.04.2012
comment
Теперь я также попытался установить для shouldRefreshRefetchedObjects значение YES, но это не сработало. Скорее всего, это не имеет никакого эффекта, потому что выборка выполняется после применения изменений к основному MOC (но до сохранения основного MOC).   -  person Alfonso    schedule 18.04.2012
comment
К сожалению, при попытке диагностировать эти проблемы между MOC вам необходимо предоставить гораздо больше информации. Все, начиная с того, как MOC передаются, в каком потоке выполняется код, как выполняются сохранения и параметры выборки, определяют, как это работает. Таким образом, вам, вероятно, следует пересмотреть свой вопрос и предоставить разделы кода для всего этого.   -  person Jody Hagins    schedule 18.04.2012
comment
Я обновил вопрос с запрошенной вами информацией. Скажи мне, если тебе нужно больше.   -  person Alfonso    schedule 18.04.2012


Ответы (1)


Несколько красных флажков для меня здесь. Во-первых, этот...

Данные регулярно обновляются в фоновом режиме, а изменения сохраняются в UIManagedDocument.managedObjectContext (с помощью PerformBlock:).

UIManagedDocuments реализуют автоматическое сохранение, и единственный раз, когда мы должны напрямую вызывать метод сохранения, — это при первоначальном создании. Все остальные «сохранения» должны выполняться через интерфейсы автоматического сохранения. По сути, это будет вызов

[document updateChangeCount:UIDocumentChangeDone];

Это делается автоматически, если вы используете UndoManager. Таким образом, если вы передаете данные непосредственно в document.managedContext, все, что вам нужно сделать, это вызвать описанный выше метод, чтобы уведомить управляемый документ о внесении изменений, а все остальное должно обрабатываться автоматически.

Во-вторых, я не уверен, что вы имеете в виду под этим:

Но как только я покажу список в дочернем контексте,

Дочерний контекст чего? Почему произвольный контекст должен что-то видеть? Является ли это дочерним контекстом основного контекста UIManagedDocument, родителем или чем-то еще?

Документация понятна (для некоторого определения ясности), но, к сожалению, она требует прочтения полного набора документации для UIDocument, UIManagedDocument и всего NSManagedOjectContext.

Когда я, наконец, осознал, что происходит (по иронии судьбы, прочитав о том, как работает iCloud), все мои проблемы, связанные с UIManagedDocument, как будто исчезли.

В общем, следуйте этим простым правилам:

  1. Никогда не вызывайте какие-либо методы «сохранения» непосредственно в любом контексте, встроенном в UIManagedDocument.

  2. Когда объекты «грязные» и их необходимо сохранить, либо зарегистрируйте изменение в диспетчере отмены, либо напрямую вызовите [doc updateChangeCount:UIDocumentChangeDone].

  3. Если вы хотите использовать дочерние контексты, продолжайте. Все, что вам нужно сделать в этом случае, это установить свойство parentContext и вызвать save: в ВАШЕМ дочернем контексте. Все остальное произойдет автоматически.

РЕДАКТИРОВАНИЕ Еще один тревожный сигнал заключается в том, что вы запускаете код с использованием разных контекстов. Если это так, вы ДОЛЖНЫ убедиться, что вы работаете из правильного потока. Объекты NSManagedObjectContext НЕ являются потокобезопасными. Таким образом, к ним всегда должен быть доступ из потока, в котором они были созданы (если NSConfinementConcurrencyType) или с использованием PerformBlock, если один из двух других типов параллелизма.

Если вы постоянно выполняете вставку/выборку с использованием нескольких контекстов, вам придется решить некоторые проблемы. В основном, вы должны убедиться, что ваши изменения распространены и запланированы для сохранения, а затем ваши выборки делают то, что вы хотите.

После завершения вставки убедитесь, что вы вызываете save ТОЛЬКО с дочерним контекстом UIManagedDocument. Никогда не вызывайте save или saveToURL для управляемого документа. Сохраните его, используя вышеупомянутые методы. Это обеспечит правильное распространение изменений.

При выборке вы должны решить, хотите ли вы, чтобы ваши выборки проходили всю цепочку контекстов. В NSFetchRequest есть множество опций (например, setShouldRefreshRefetchedObjects). Эти параметры будут определять, будет ли выборка использовать только свой собственный контекст или пойдет в резервное хранилище и многое другое. Значения по умолчанию — это то, что вам нужно в большинстве случаев, но при использовании дочерних контекстов на вас ложится больше ответственности.

Кроме того, если у вас есть дочерние контексты, вы ДОЛЖНЫ убедиться, что вы создаете их с правильным типом параллелизма и что вы используете их только из соответствующего потока/очереди. В противном случае вы получите неопределенные результаты.

ИЗМЕНИТЬ

В ответ на это заявление из вашего недавнего редактирования вопроса:

3) Когда пользователь хочет внести изменения, создается новый MOC как дочерний элемент MOC документа. Это MOC основной очереди. Это контекст, который используется для выполнения показанной выше выборки.

Я никогда этого не делал, и для меня это еще один красный флаг. MOC основной очереди уже существует — document.managedObjectContext. Используйте это, если хотите испортить документ в основном потоке. Помните, что CoreData по-прежнему работает по модели сдерживания потоков и действительно требует только одного MOC на поток. Новый API предоставляет способы связывания MOC с очередями и помогает, но я готов поспорить, что наличие нескольких MOC на поток может вызвать проблему. В общем, я бы этого избегал.

person Jody Hagins    schedule 17.04.2012
comment
Спасибо за ваш вклад. Я обновил вопрос, чтобы решить некоторые из ваших вопросов. Я не вызываю -save: на moc, только из дочерних контекстов. Я никогда не вызываю -updateChangeCount:, поэтому я собираюсь выяснить, решит ли это мои проблемы. - person Alfonso; 18.04.2012
comment
При установке includePendingChanges на NO ошибка исчезает, но теперь обновления просто не отображаются, пока не произойдет сохранение. Я попытался заставить контекст обработать их, отправив -processPendingChanges, но это, похоже, не помогает. - person Alfonso; 18.04.2012
comment
О нескольких MOC основной очереди. Я сделал это, чтобы иметь блокнот, где я могу вносить изменения, не затрагивая остальную часть приложения и отбрасывая их позже, если они не нужны. Но теперь я смотрю на NSUndoManager, который в основном обеспечивает аналогичную цель. - person Alfonso; 23.04.2012
comment
Я пошел с NSUndoManager, и теперь он работает нормально. Не уверен, однако, почему проблематична работа с несколькими управляемыми контекстами в одном потоке, но я рад, что мои проблемы теперь решены. Большое спасибо за вашу помощь и терпение! - person Alfonso; 23.04.2012