I'm getting outdated results when executing a fetch request upon a NSManagedObjectContextObjectsDidChangeNotification
.
As an example, consider directories with documents that can be deleted logically with a boolean attribute (softDeleted
). The fetch request should return all directories that have at least one non-deleted document (directoriesWithDocuments
).
The initial state is single directory with a single deleted document. directoriesWithDocuments
returns an empty array.
The following code restores the document by setting the softDeleted
boolean to NO
.
[_context.undoManager beginUndoGrouping];
[_context.undoManager setActionName:@"undelete"];
document.softDeleted = @(NO);
NSError *error;
BOOL success = [_context save:&error]; // This triggers the notification
[_context.undoManager endUndoGrouping];
The save triggers a NSManagedObjectContextObjectsDidChangeNotification
. I expected directoriesWithDocuments
to return the directory, but instead it still returns an empty array.
- (void)objectsDidChangeNotification:(NSNotification*)notification
{
NSArray *objects = [self directoriesWithDocuments]; // Still empty!
}
Yet, if I execute directoriesWithDocuments
after saving the context, either right away or in the next run loop, it returns the directory as expected.
This is the code of the fetch request:
- (NSArray*)directoriesWithDocuments
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY documents.softDeleted == NO"];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Directory"];
fetchRequest.predicate = predicate;
fetchRequest.includesPendingChanges = YES; // Just in case
NSError *error = nil;
NSArray *objects = [_context executeFetchRequest:fetchRequest error:&error];
return directories;
}
I suspect that the context has some kind of cache for fetch requests that is not cleared until the notification is handled. Is this how Core Data is expected to behave? Or am I doing something wrong?
I'm currently delaying the execution of the fetch request like this:
- (void)objectsDidChangeNotification:(NSNotification*)notification
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// HACK: Give time to Core Data to process pending changes or invalidate caches
NSArray *objects = [self directoriesWithDocuments]; // Returns the directory as expected
}];
}
Per @MikePollard's suggestion, I checked the returned value of directoriesWithDocuments
in NSManagedObjectContextWillSaveNotification
and NSManagedObjectContextDidSaveNotification
. The results are (in order):
NSManagedObjectContextObjectsDidChangeNotification
: empty (wrong)NSManagedObjectContextWillSaveNotification
: empty (wrong)NSManagedObjectContextDidSaveNotification
: 1 directory (correct)It strikes me that the predicate is going to be translated into a SQL statement so until the save hits the DB you're not going to get the result you want ... (Not that'd you'd image that from just reading the NSFetchRequest
documentation.)
So try doing the fetch as a result of the NSManagedObjectContextDidSaveNotification
.
I bet if you turn on -com.apple.CoreData.SQLDebug 1 you'll see the SQL statement.