NSFetchedResultsController not firing delegate method after merging update from background thread - objective-c

I have an NSFetchedResultsController and a few operations that inserts and updates managed objects on separate threads via NSOperationQueue.
The FRC looks like this, note that I have set the cache to nil:
[NSFetchedResultsController deleteCacheWithName:nil];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
Each threaded operation has its own managed object context and fires mergeChangesFromContextDidSaveNotification to the main MOC each time it saves changes.
The code that I use to merge contexts looks like this:
- (void)mergeContextChanges:(NSNotification *)notification
{
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
if([NSThread isMainThread] == NO)
{
[self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO];
return;
}
NSSet *updated = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
for(NSManagedObject *thing in updated)
{
NSLog(#"Background thread updated %#", [thing description]);
}
for(NSManagedObject *thing in updated)
{
[[context objectWithID:[thing objectID]] willAccessValueForKey:nil];
}
[context mergeChangesFromContextDidSaveNotification:notification];
}
I can confirm by looking at the logs that each time the background operations insert or update data, my mergeContextChanges: method is being called with the proper insert/update values.
The problem is that while merging inserts are firing the FRCs delegate methods (ex. controllerDidChangeContent:) properly, merging updates doesn't signal the FRC to fire its delegate methods.
Strange enough, I can also confirm that the FRC fires its delegates properly if I run the updates on the main thread using the main MOC.
How can I make the FRC fire its delegate methods when updated MOCs are merged?
More Info: Looks like using any MOC other than the main MOC and trying to merge updates to the main MOC has the same results; the FRC refuses to notice it.

Oh..... man.
It looks like accessing the main MOC from within my threaded operations (even if it's for a task unrelated to the data I was trying to update) causes this weird behavior.
I hope this helps anyone else that runs into this problem.

Related

NSManagedObjectContext working in background

I have problem with merging changes between two context. One of them is working in background(additional context) as WebServices reading and writing data to database. While I was trying save any changes in main context, application gone stuck and nothing happend. Context from web services(singleton) I create as I showed below. When I try save main context I send notification to merge changes with WebServices context but it does not work correctly. Whats wrong? It happend when background is reading data and main context try to save something i database.
managedObjectContext = [NSManagedObjectContext new];
NSPersistentStoreCoordinator *store = [ [theDelegate managedObjectContext] persistentStoreCoordinator];
[managedObjectContext setPersistentStoreCoordinator:store];
[managedObjectContext setStalenessInterval:0.0];
Before save of Main Context I Use:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:self.managedObjectContext];
where I merge changes
[[[WebServie instance] managedObjectContext] mergeChangesFromContextDidSaveNotification:notification]
It should working but I have no idea whats going wrong, WebServices are working on background, and DataBaseManager is in main thread
Thats code of my merge context is it wrong?
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserverForName:NSManagedObjectContextDidSaveNotification
object: self.managedObjectContext queue:nil
usingBlock:^(NSNotification *notification)
{
NSLog(#"merge");
[[[WebServiceManager instance] managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}];
NSError *error;
NSLog(#"error");
if (![self.managedObjectContext save:&error])
{
NSLog(#"error :%#", error);// Update to handle any error appropriately.
}
NSLog(#"after error");
[dnc removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:self.managedObjectContext];
You need to do the merging on the queue of the context that's supposed to do the merge. Not on the queue (or thread) that it's being sent on.
If you are trying to keep two context synchronized, you need to listen to the NSManagedObjectContextDidSaveNotification for both context, and merge the notification of the first context to the second and viceversa. So, let's say you have your context on your applicationDelegate, and you have a context on your web singleton. You would create a method on your applicationDelegate to be fired when the singleton context saves. On that method, you would take the notification from the singleton object and merge it with the main thread context. On your singleton, you would listen to the NSManagedObjectContextDidSaveNotification from the main thread and merge it with the singleton's context.

FetchedResultsController doesn't see the changes in managedObjectContext after data import

I'm working on the data import part in my app, and to make the UI more reliable, i followed this Marcus Zarra article
http://www.cimgf.com/2011/08/22/importing-and-displaying-large-data-sets-in-core-data/
The idea is that you make the import in a separate context in the background tread(i use GCD for that), and your fetchedResultsController's context merges the changes by observing the NSManagedObjectContextDidSaveNotification.
The issue i get is very strange to me - my fetchedResultsController doesn't get those changes itsef and doesn't reload the TableView when the new data comes.
But if i fire the following method, which makes the fetch and reloads the table - it gets it all there.
- (void)updateUI
{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
[self.tableView reloadData];
}
So now i call that method when i get the NSManagedObjectContextDidSaveNotification to make it work, but it looks strange and nasty to me.
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == [self managedObjectContext]) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(contextChanged:) withObject:notification waitUntilDone:NO];
return;
}
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
//TODO:Make it work as it should - merge, without updateUI
[self updateUI];//!!!Want to get rid of this!
}
Why can it be like this?
Here is the code that is responsible for parsing the data and adding the Observer.
- (void)parseWordsFromServer:(NSNotification *)notification
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) , ^{
NSDictionary *listInJSON = [notification userInfo];
wordsNumbers = [[listInJSON valueForKey:#"words"]mutableCopy];
if ([wordsNumbers count])
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
//New Context for the new thread
NSManagedObjectContext *backContext = [[AppDelegate sharedAppDelegate]backManagedObjectContext];
//Get all the words we already have on this device
NSArray *wordsWeHave = [Word wordsWithNumbers:wordsNumbers inManagedContext:backContext];
//Add them to this list
for (Word *word in wordsWeHave)
[[List listWithID:[currentList listID] inManagedObjectContext:backContext]addWordsObject:word];
[backContext save:nil];!//Save the context - get the notification
}
});
}
EDIT
I use the NSFetchedResutsControllerDelegate, indeed, how else could i pretend my tableview to be updated if i didn't?
UPDATE Decided just to move to Parent - Child paradigm
The problem has been discussed many times, like NSFetchedResultsController doesn't show updates from a different context, and it's quite difficult to understand what is going on but I have few notes.
First, you are violating a simple rule: you need to have a managed object context per thread (Concurrency with Core Data Section).
Create a separate managed object context for each thread and share a
single persistent store coordinator.
So, inside your custom thread access the main context, grab its persistent coordinator and set it to the new context.
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:persistentStoreCoordinatorGrabbedFromAppDelegate];
Second, you don't need to register
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
within the new thread. Just register for it within the class that create the new thread (or in the app delegate).
Finally, if you are not using a NSFetchedResutsControllerDelegate, use it. It allows to get rid of reloading data table. When the context changes, the delegate responds to changes: edit, remove, add.
Starting from iOS 5, you could just use new Core Data API and make your life easier with new confinement mechanism.
Edit
From #mros comment.
Multi-Context CoreData
It may help you understand a little bit more about the advantages of
using a parent-child core data model. I particularly like the bit
about using a private queue context to handle the persistent store.
Make sure to read down through the whole thing because the beginning
shows how not to do it.
Hope that helps.

Managed Object Context not saving to persistant store

I have a threaded operation that creates a new managed object, saves it to the persistant store, then passes the objectID of the new objected via NSNotification to the main thread for further processing
However, when I try to access the newly created managed object from the main thread all the values that I set on the background thread return as empty.
** background thread
// create a new managed object context for this thread
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
[context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
// create the object
MyObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:#"MyObject" inManagedObjectContext:context];
[newManagedObject setAValue:#"A"];
[newManagedObject setBValue:#"B"];
[newManagedObject setCValue:#"C"];
// save it on the main thread
[context performSelectorOnMainThread:#selector(save:) withObject:nil waitUntilDone:NO];
// post notification to main thread, pass the objectID
NSMutableDictionary *userInfo = [NSDictionary dictionaryWithObject:[newManagedObject objectID] forKey:#"objectID"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"doneInsertingObject" object:userInfo];
[context release];
** main thread
...
// register notification for background thread
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mergeContextChanges:) name:NSManagedObjectContextDidSaveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSomethingWithObject:) name:#"doneInsertingObject" object:nil];
...
- (void)doSomethingWithObject:(NSNotification*)noif
{
if([NSThread isMainThread] == NO)
{
// run this on the main thread
[self performSelectorOnMainThread:_cmd withObject:noif waitUntilDone:NO];
return;
}
// get managed object from objectID
NSDictionary *userInfo = [noif userInfo];
MyObject *object = (MyObject*)[appDelegate.managedObjectContext objectWithID:[userInfo valueForKey:#"objectID"]];
[appDelegate.managedObjectContext refreshObject:object mergeChanges:YES];
// these should return 'A, B, C' but all three return 'nil'
NSLog(#"aValue: %#", object.aValue);
NSLog(#"bValue: %#", object.bValue);
NSLog(#"cValue: %#", object.cValue);
}
// merge background thread moc with main moc
- (void)mergeContextChanges:(NSNotification *)notification
{
if([NSThread isMainThread] == NO)
{
// run this on the main thread
[self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO];
return;
}
// fault all updated objects
NSSet *updated = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
for(NSManagedObject *thing in updated)
{
[[appDelegate.managedObjectContext objectWithID:[thing objectID]] willAccessValueForKey:nil];
}
// merge changes to the main managed object context
[appDelegate.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
// force processing of any pending changes
[appDelegate.managedObjectContext processPendingChanges];
}
I've tried changing merge policies and there was no difference.
I've tried adding logging to the context merge method and I have confirmed receiving a "inserted" notification from the background thread before the doSomethingWithObject: method on the main thread is called.
Why is my data not being updated to the persistant store?
I can't see where you save the context for your background thread. If it's this line
// save it on the main thread
[context performSelectorOnMainThread:#selector(save:) withObject:nil waitUntilDone:NO];
I don't know if it is correct. You have save the context from the thread that has created it and not in the main thread.
[context save:&error];
For further info, I suggest you to read the articles by Marcus Zarra importing-and-displaying-large-data-sets-in-core-data. You can find the sample code at the end. In addition you can find further info in using-core-data-on-multiple-threads.
Hope it helps.
Your NSManagedObjectContext has to be saved on the thread it was created on (as #Flex_Addicted pondered).
After it has saved on the background thread, a notification will be posted telling you to merge changes from the background context to the main context.
Apples documents read "Saving in a Background Thread is Error-prone" - this isn't related to using another NSManagedObjectContext. They are saying that if you have 1 context, and you try to dispatch the save operation to the background - THAT is error prone. If you have multiple contexts, they should only be used within one thread at a time.

Confused about how fetchResultController works with MagicalRecord

I have a NSOperation subclass,this is the main method:
(void)main
{
NSAutoreleasePool *Pool = [[NSAutoreleasePool alloc] init];
managedObjectContext = [NSManagedObjectContext contextThatNotifiesDefaultContextOnMainThread];
Message *message = (Message *) [managedObjectContext objectWithID:self.messageID];
message.status = [NSNumber numberWithInt:SKMessageSendStateStart];
[message save];
[self send];
[self finish];
[Pool drain];
}
I define the fetchResultController and defaultContext like this:
(BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[MagicalRecordHelpers setupCoreDataStackWithStoreNamed:#"Shark"];
self.context = [NSManagedObjectContext context];
[NSManagedObjectContext setDefaultContext:self.context];
self.fetchController = [Message fetchRequestAllGroupedBy:nil withPredicate:nil sortedBy:#"text" ascending:YES];
[self.fetchController setDelegate:self];
[self.fetchController performFetch:nil];
}
Everytime i call [message save],the console logout:
-NSManagedObjectContext(MagicalRecord) mergeChangesFromNotification: Merging changes to * DEFAULT
context on Main Thread *
But the NSFetchedResultsControllerDelegate never get called!
Does this mean that I set the FetchedResultsController wrong or what? I
am totally confused.
Thanks in advance.
The reason this isn't working is because MagicalRecord will automatically call performFetch: for you, thus not allowing you to set the delegate ahead of time.
Also, in your applicationDidFinishLaunching: method, you want to delete these lines:
self.context = [NSManagedObjectContext context];
[NSManagedObjectContext setDefaultContext:self.context];
You want to NOT change the default context in this case. MagicalRecord is handling stuff for you when you call setupCoreDataStackWithStoreNamed:...that is, a MOC is already available for use when that method completes, there is no need to toss the one it created for you and set the default context to a new instance in this particular case.
It's also not necessary to hold on to the context if all you're doing is going to use it to pass to one of the fetch methods provided by MagicalRecord. MagicalRecord will create a single context for its use (the 'default' context) and just use that...
I misunderstood what the [NSManagedObjectContext context] means. It create a new context in the main thread. Since the context that the fetchResultController monitor is not the same context that the change merged to, the NSFetchedResultsControllerDelegate will not get called.
Change [NSManagedObjectContext context] to [NSManagedObjectContext defaultContext] solved the problem.

How to correctly handle threading when drawing Core Data entity information with CATiledLayer

I'm contemplating how to offload the drawing of a very large Core Data tree structure to CATiledLayer. CATiledLayer seems to be awesome because it performs drawing on a background thread and then fades in tiles whenever they're drawn. However, because the information of the drawing comes from a Core Data context that is by design not thread safe, I'm running into race condition issues where the drawing code needs to access the CD context.
Normally, if I need to perform background tasks with Core Data, I create a new context in the background thread and reuse the existing model and persistent store coordinator, to prevent threading issues. But the CATiledLayer does all the threading internally, so I don't know when to create the context, and there needs to be some kind of context sharing, or I can't pass the right entities to the CATiledLayer to begin with.
Is there anyone with a suggestion how I can deal with this scenario?
Cheers,
Eric-Paul.
The easiest solution is to use the dispatch API to lock all of your data access onto a single thread, while still allowing the actual drawing to be multi-threaded.
If your existing managed object context can only be accessed on the main thread, then this is what you do:
- (void)drawInContext:(CGContextRef)context // I'm using a CATiledLayer subclass. You might be using a layer delegate instead
{
// fetch data from main thread
__block NSString *foo;
__block NSString *bar;
dispatch_sync(dispatch_get_main_queue(), ^{
NSManagedObject *record = self.managedObjecToDraw;
foo = record.foo;
bar = record.bar;
});
// do drawing here
}
This is a quick and easy solution, but it will lock your main thread while fetching the data, which is almost certainly going to create "hitches" whenever a new tile is loaded while scrolling around. To solve this, you need to perform all of your data access on a "serial" dispatch queue.
The queue needs to have it's own managed object context, and you need to keep this context in sync with the context on your main thread, which is (presumably) being updated by user actions. The easiest way to do this is to observe a notification that the context has changed, and throw out the one used for drawing.
Define an instance variable for the queue:
#interface MyClass
{
NSManagedObjectContext *layerDataAccessContext;
dispatch_queue_t layerDataAccessQueue;
}
#end
Create it in your init method:
- (id)init
{
layerDataAccessQueue = dispatch_queue_create("layer data access queue", DISPATCH_QUEUE_SERIAL);
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextDidChange:) name:NSManagedObjectContextDidSaveNotification object:nil]; // you might want to create your own notification here, which is only sent when data that's actually being drawn has changed
}
- (void)contextDidChange:(NSNotification *)notif
{
dispatch_sync(layerDataAccessQueue, ^{
[layerDataAccessContext release];
layerDataAccessContext = nil;
});
[self.layer setNeedsDisplay];
}
And access the context while drawing:
- (void)drawInContext:(CGContextRef)context
{
// fetch data from main thread
__block NSString *foo;
__block NSString *bar;
dispatch_sync(layerDataAccessQueue, ^{
NSManagedObject record = self.managedObjectToDraw;
foo = record.foo;
bar = record.bar;
});
// do drawing here
}
- (NSManagedObject *)managedObjectToDraw
{
if (!layerDataAccessContext) {
__block NSPersistentStoreCoordinator *coordinator;
dispatch_sync(dispatch_get_main_queue(), ^{
coordinator = [self persistentStoreCoordinator];
});
layerDataAccessContext = [[NSManagedObjectContext alloc] init];
[layerDataAccessContext setPersistentStoreCoordinator:coordinator];
}
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity =
[NSEntityDescription entityForName:#"Employee"
inManagedObjectContext:layerDataAccessContext];
[request setEntity:entity];
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"self == %#", targetObject];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *array = [layerDataAccessContext executeFetchRequest:request error:&error];
NSManagedObject *record;
if (array == nil || array.count == 0) {
// Deal with error.
}
return [array objectAtIndex:0];
}
I've given up trying to share managed object context instances between CATiledLayer draws and now just alloc/init a new context at every call of drawLayer:inContext: The performance hit is not noticable, for the drawing is already asynchronous.
If there's anyone out there with a better solution, please share!