Question

I have a UIManagedDocument with some data which I display in a list using NSFetchedResultsController. The data is regularly updated in the background and changes are put onto the UIManagedDocument.managedObjectContext (using performBlock:).

When I display the data from the main context of the document, all works as expected. But as soon as I display the list in a context which is a child of the main context (child.parentContext = document.managedObjectContext), I don't see any objects and the following error is printed on the console:

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'

This only happens after a new object was inserted into the documents contact. When I wait long enough for the automatic save to happen, the list displays fine. Also the problem is only when I have a sectionNameKeyPath set on the NSFetchedResultsController and only with the child context.

This is how I setup the fetched results controller, nothing fancy so I don't see what I could do wrong here:

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] and [Contact userDefinedSectionNameKeyPath] are resolved at runtime. The sort descriptors contain as first entry the sectionNameKeyPath. Neither can be nil or other funny stuff.

Edit: Clarified some vague parts. Specifically, I don't call -save: on the documents managed object context.

Edit 2: I'll try to explain how the MOCs relate to each other.

There are three managed object contexts in play:

1) The UIManagedDocument.managedObjectContext created by loading the document.

2) There is a background thread running which updates the objects from time to time. This is a private queue moc with the documents MOC as the parent:

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

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

3) When the user wants to make changes a new MOC is created as a child of the document MOC. This is a main queue MOC. This is the context which is used to perform the fetch shown above.

The background updates are done from an NSOperationQueue but all access to the background MOC is properly surrounded with a -performBlock:. All other accesses are done from the main thread.

Edit 3: While playing around with some settings on NSFetchRequest I found that the problem goes away when setting fetchRequest.includesPendingChanges = NO. But this is not a viable solution, because now the user doesn't see any updates anymore until the changes are saved in the background by UIManagedDocument.

Was it helpful?

Solution

Several red flags to me here. First, this one...

The data is regularly updated in the background and changes are saved onto the UIManagedDocument.managedObjectContext (using performBlock:).

UIManagedDocuments implement auto-save, and the only time we should directly call a save method is on initial creation. All other "saves" should be done via the automatic-save interfaces. Basically, that would be calling

[document updateChangeCount:UIDocumentChangeDone];

This is done automatically if you use an UndoManager. So, if you are feeding directly into the document.managedContext, all you do is call the above method to notify the managed document that changes are done, and everything else should be handled automatically.

Second, I'm not sure what you mean by this:

But as soon as I display the list in a child context,

A child context of what? Why should an arbitrary context see anything? Is it a child context of the main context of the UIManagedDocument, or the parent, or something else?

The documentation is clear (for some definition of clear), but, unfortunately, it requires reading the complete documentation set for UIDocument, UIManagedDocument, and all the NSManagedOjectContext stuff as well.

When I finally wrapped my head around what was happening (by, ironically, reading about how iCloud works), all my problems related to UIManagedDocument seemed to disappear.

Basically, follow these simple rules:

  1. Never call any "save" methods directly on any context embedded in a UIManagedDocument.

  2. When objects are "dirty" and need to be saved, either register the change with the undo-manager, or directly call [doc updateChangeCount:UIDocumentChangeDone].

  3. If you want to use child contexts, go right ahead. All you need to do in this case, is set the parentContext property, and call save: on YOUR child context. Everything else will happen automatically.

EDIT Another red flag is that you are running the code using different contexts. If that is the case, you MUST make sure you are running from the right thread. NSManagedObjectContext objects are NOT thread safe. Thus, they must always be accessed from the thread in which they were created (if NSConfinementConcurrencyType) or using performBlock if one of the other two concurrency types.

If you are continuously inserting/fetching using multiple contexts, you will have some issues to resolve. Mainly, you have to make sure your changes are propagated and scheduled to be saved, and then that your fetches do what you want them to do.

When done inserting, make sure you call save ONLY with a child context of the UIManagedDocument. Never call save or saveToURL on the managed document. Save to it using the above mentioned methods. This will ensure you are correctly propagating changes.

When fetching, you have to decide if you want your fetches to go all the way up the chain of contexts. There are lots of options on NSFetchRequest (like setShouldRefreshRefetchedObjects). Those options will decide if the fetch just uses its own context, or goes to the backing store, and lots of other stuff. The defaults are what you want most of the time, but when using child contexts, you have more responsibility.

Also, if you have child contexts, you MUST make sure you create them with the right concurrency type, and that you are only using them from within the proper thread/queue. You will get undefined results otherwise.

EDIT

In response to this statement from your recent question edit:

3) When the user wants to make changes a new MOC is created as a child of the document MOC. This is a main queue MOC. This is the context which is used to perform the fetch shown above.

I have never done this, and, to me, is another red-flag. There is already a main queue MOC - the document.managedObjectContext. Use that if you want to muck with the document in the main thread. Remember, CoreData still operates on a thread-containment model, and really wants only one MOC per thread. The new API provides ways to have MOCs associated with queues, and helps, but I would bet underneath that having multiple MOCs per thread can cause a problem. In general, I would avoid that.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top