NSFetchedResultsController doesn't show updates from a different context
Solution 1
You have the basic idea, so there is probably a bug somewhere in your code...
Double check that you are properly registering to receive the notification from the background MOC.
Register to receive all notifications from all objects. In that method, log the event, and all its data. When the object is a MOC, dump all its properties (especially the lists of registered, inserted, updated, and deleted objects).
Put a log statement right before and after the save call, and in the notification handler for merging the notification.
Also, you omitted a lot of code so it's hard to know what you are actually doing, but the code sample you included looks like it is hard setting isSync to YES for all objects being loaded, but your fetch request only wants those with isSync set to NO. None of those new objects will pass that predicate.
Finally, double check your model definition and make sure you are using the right number type. This can be a big source of problems.
EDIT
Oh yeah, I forgot... your fetch request does not have a sort descriptor. When you create a FRC, your fetch request must contain at least one sort descriptor... if you have multiple sections, the first sort descriptor is used to group the objects into sections.
To follow up on Alexsander's comment... I alluded to it at the beginning of my post, but you certainly do not want to listen to notifications from a MOC unless it is well known as one of yours (unless, of course, you are just logging for debugging purposes). You should know about the MOC you are using.
Furthermore, I would suggest using parent/child MOCs for this type of processing, but what you are doing should work if done properly.
Parent (private concurrency type) Main (main concurrency type)
Then, with your background MOCs, just have them set the main moc as their parent. When they save, their objects get injected directly into the main MOC. The main MOC can then issues saves at later times to put them onto disk.
Or, you can parent you background MOC to the "parent" and then the "main" MOC can just reissue the fetch to get the data from the parent.
Solution 2
I just had the same problem, which I solved with parent/child contexts. Here is the problem I had.
I was updating my Core Data object graph in a background thread which had its own managedObjectContext
(which is mandatory), and my fetchedResultsController
was not able to pick up the changes done to the database.
After I solved it I took some notes:
ManagedObjectContext
s are not thread safe, which means that a managedObjectContext
cannot be shared with other threads. If a thread needs to use a managedObjectContext
, then it will initialize its own managedObjectContext.
To initialize a managedObjectContext
there are two ways:
alloc/init
then set itspersistentStoreCoordinator
propertyalloc/init
then set aparentContext
property instead of apersistentStoreCoordinator
property
note: one cannot set both the persistentStoreCoordinator
and the parentContext
property of a managedObjectContext
.
Using parent/child contexts is needed when the a context is run on a background thread that is "linked to controllers and UI objects that are required to be used only on the main thread"(core data documentation).
Here are the requirements needed for the parent/child contexts:
the parent context lives in the main thread
the child context lives in the background thread
both contexts need to be initialized with an
initWithConcurrencyType:NSMainQueueConcurrencyType
method.-
when the batch of changes has been done in the child context, both contexts need to perform a save operation. These save operations need to be nested in performBlock methods, i.e:
childContext performBlock:^{ [childContext save:nil]; [self.parentContext performBlock:^{ [self.parentContext save:nil]; }]; }];
EDIT: The code above is actually a bad idea for 2 reasons:
1) It works without saving the parent context.
2) The main thread is blocked if the parent context runs on it.
I hope it helped !
EDIT: Here is a stackoverflow thread which helped me greatly: Does a Core Data parent ManagedObjectContext need to share a concurrency type with the child context?
Related videos on Youtube
Comments
-
Lorenzo B over 4 years
I have an
NSFetchedResultsController
and a few operations updates managed objects on separate threads viaNSOperationQueue
.The FRC (with its predicate) looks like this:
- (NSFetchedResultsController*)fetchedResultsController { if(fetchedResultsController) return fetchedResultsController; NSManagedObjectContext* mainContext = [self managedObjectContext]; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; [fetchRequest setEntity:[NSEntityDescription entityForName:@"Check" inManagedObjectContext:mainContext]]; [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"isSync == %@", [NSNumber numberWithBool:NO]]]; [fetchRequest setFetchBatchSize:10]; fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil]; fetchedResultsController.delegate = self; [fetchRequest release], fetchRequest = nil; return fetchedResultsController; }
The main thread and the threaded operation have their own managed object contexts. They only share the same coordinator.
Within the threaded operation I change the
isSync
property fromNO
toYES
. To know what isCheck
entity to update, the main context passes to the threaded one aNSManagedObjectID
. The threaded operation retrieves the managed object like the following:-(void)main { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSManagedObjectContext *exportContext = [[NSManagedObjectContext alloc] init]; [exportContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]]; //... Check* check = (Check*)[exportContext existingObjectWithID:objID error:&error]; check.isSync = [NSNumber numberWithBool:YES]; //... [exportContext save:&error]; [pool release], pool = nil; }
When the thread operation calls a
save
themergeChangesFromContextDidSaveNotification
notification is called and the main context merges the changes.- (void)contextChanged:(NSNotification*)notification { if ([notification object] == [self managedObjectContext]) return; if (![NSThread isMainThread]) { [self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES]; return; } [[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification]; }
Logging the description of the
notification
leads to verify that changes are performed correctly.My problem
Delegates methods of
NSFetchedResultsControllerDelegate
are not called.This is quite strange since dealing with the same context, the main one, allows to listen for changes and delegates methods are called, e.g. deleting a row object in the
UITableView
.I've found some topics on SO with the same problem. I've tried all the workarounds but I cannot find a valuable solution:
NSFetchedResultsController not showing updates from other contexts
NSFetchedResultsController not firing delegate method after merging update from background thread
Thank you in advance.
Edit
The code above was working in a previous model. Then I created a new model copying (and pasting) entities from the previous one and now it doesn't work anymore.
Suggestions?
Edit 2
This is the predicate I'm using in
NSFetchedResultsController
getter. It's my fault, but when I wrote the post I didn't copy it.NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"insertionDate" ascending:NO]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; // previous code here [fetchRequest setSortDescriptors:sortDescriptors];
Now, about Jody last comment
In the main() of your NSOperation, you are loading new objects, and in there it looks like you are setting isSync to YES for each new object. The predicate you use for the fetchedResultsController is looking only for objects that have isSync == NO.
I expecting that when the property
isSync
is set to YES, theNSFetchedResultsController
observes that changes and removes rows that not match the predicate. Am I wrong?Remember that when merging changes from the background to the main thread, I'm able to see that few objects have updated their
isSync
property.-
Artur Friesen over 10 yearsSee my answer for this Question. I think this is could help: stackoverflow.com/questions/3923826/…
-
Sjors Provoost almost 10 yearsFrom reading your question I think @ArturFriesen is right: NSFetchedResultsController ignores changes which happen outside it's own or it's child context. You'll need to call refreshObject on every relevant object in the main moc. Another method to do that: stackoverflow.com/a/21378533/313633
-
Alexsander Akers about 12 yearsMany frameworks use Core Data internally. Make sure you don't merge those notifications by mistake.
-
Lorenzo B about 12 years@JodyHagins First of all thank you for your answer. + 1 for your support. What I wrote in the edit, I think, it's not a problem. Maybe I've changed something but I don't know what. Could you explain what do you mean with ...but the code sample you included looks like it is hard setting isSync to YES for all objects being loaded, but your fetch request only wants those with isSync set to NO. None of those new objects will pass that predicate.? Thank you in advance.
-
Lorenzo B about 12 years@AlexsanderAkers Thank you for your comment.
-
Jody Hagins about 12 yearsIn the main() of your NSOperation, you are loading new objects, and in there it looks like you are setting isSync to YES for each new object. The predicate you use for the fetchedResultsController is looking only for objects that have isSync == NO. Also, please address the other issues I raised (especially the missing sort descriptor).
-
Lorenzo B about 12 years@JodyHagins Thank you Jody. I added an edit (Edit 2). Cheers.
-
Jody Hagins about 12 yearsUsing CoreData can be so complex, when you have a problem, you almost have to provide all code because so many things are tightly coupled. I would set a breakpoint (or NSLog) right before the save, and dump the entire moc that is being saved (I actually have helper code to do this it is so common for me). My canonical debugging process for CoreData: I have subclasses for NSManagedObjectContext and NSPersistentStoreCoordinator that just NSLog and call super. I also register for every notification, and NSLog them. I suggest you do something similar to get a hand on what is really happening.
-
Lorenzo B about 12 years@JodyHagins I'll investigate a bit. About what I wrote I expecting that when the property isSync is set to YES, the NSFetchedResultsController observes that changes and removes rows that not match the predicate. Am I wrong? Thank you for your support.
-
Lorenzo B over 11 yearsthanks for sharing +1, I don't understand both contexts need to be initialized with an initWithConcurrencyType:NSMainQueueConcurrencyType method.. What does it mean?
-
skozin about 11 yearsboth contexts need to be initialized with an initWithConcurrencyType:NSMainQueueConcurrencyType method — this is wrong.