NSManagedObjectContext working in background - objective-c

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.

Related

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.

NSFetchedResultsController not firing delegate method after merging update from background thread

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.

Changes saved from one NSManagedObjectContext doesn't reflect on main NSManagedObjectContext

I have a main NSManagedObjectContext that's created in the appDelegate.
Now, I'm using another NSManagedObjectContext for editing/adding new objects without affecting the main NSManagedObjectContext, until I save them.
When I save the second NSManagedObjectContext, the changes are not reflected in the main NSManagedObjectContext, yet if I open the .sqlite database from simulator, the changes have been saved correctly into the .sqlite database. It doesn't matter if I fetch the data again, or even if I create a third NSManagedObjectContext, I cannot see those changes from the second NSManagedObjectContext, despite the fact those changes do actually exist on disk at this point.
If I quit and re-open the app, all changes are there.
What can cause the main NSManagedObjectContext to not see the new changes present in the persistent store?
Before this approach, I was using a single NSManagedObjectContext and undoManager, but I wanted to change it to use two different NSManagedObjectContexts.
The second NSManagedObjectContext save:
NSError* error = nil;
if ([managedObjectContext hasChanges]) {
NSLog(#"This new object has changes");
}
if (![managedObjectContext save:&error]) {
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
}
else {
NSLog(#" %#", [error userInfo]);
}
}
If you haven't already done so, I suggest reading the Apple documentation on Core Data : Change Management.
You need to notify the first context of the changes that were saved through the second context. When saving a context, it posts a NSManagedObjectContextDidSaveNotification. Register for that notification. In the handler method, merge into the first context the changes saved through the second context. For example:
// second managed object context save
// register for the notification
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(handleDidSaveNotification:)
name:NSManagedObjectContextDidSaveNotification
object:secondManagedObjectContext];
// rest of the code ommitted for clarity
if (![secondManagedObjectContext save:&error]) {
// ...
}
// unregister from notification
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:secondManagedObjectContext];
Notification handler:
- (void)handleDidSaveNotification:(NSNotification *)note {
[firstManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}

iphone sdk communicate between thread

My application has a second running thread. I need to achieve the following :
Stop the separate thread gracefully from the main application thread
Call a function on the main thread from the second thread to signal a result has been found and pass it to the main one.
I've found the following for the first task : share a global variable between the 2 threads ?
No idea how to achieve the second task. (NSNotificationCenter doesn't allow to pass objects ...)
I'm lunching the second thread like this [NSThread detachNewThreadSelector:#selector(backGroudTask) toTarget:self withObject:nil];
Thanks
I'm still searching for the best answer to this, but here is what I do:
Use NSLock to create a lock that prevents me from accessing the same variable on both threads. Then use a BOOL to see if the main thread wants to initiate a stop.
in main thread do this
[myLock lock];
exitFlag = YES;
[myLock unlock];
in the other thread do this
endMe = NO;
while(!endMe)
{
// do your task stuff
[myLock lock];
endMe = exitFlag;
[myLock unlock];
}
For the second part of your question use the following:
[self performSelectorOnMainThread:#selector(your_selector_name) withObject:nil waitUntilDone:false];
This will cause the your selector routine to run on the main thread.
Hope this helps
(NSNotificationCenter doesn't allow to pass objects ...)
it does, but you have to add them to the userinfo of the notification
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:myObject forKey:#"object"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:userInfo];
- (void)foo:(NSNotification *)notification {
id object = [[notification userInfo] objectForKey:#"object"];
}