I\'m trying to download some JSON objects in the background and am doing quite a bit of multi threading. Once the operation completes, I noticed that this assertion fails:
In my situation, I'm using a subclass of RKObjectLoader and there's way too many threads and operations to keep track of what's going on. I found that the RKObjectStore can be asked nicely to merge changes for me by asking the loaded object to save itself. (before I was asking [AppUser managedObjectContext]
to save itself, which was wrong)
The correct solution involved asking the loaded object to save itself in its own context as follows:
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
AppUser* user = nil;
for (id object in objects)
{
user = object;
}
NSError* error = nil;
/**this call fires the:
// - (void)mergeChanges:(NSNotification *)notification
within the rkmanagedobjectstore class and merges changes made in this background operation over to the main contxt
*/
[[user managedObjectContext] save:nil];
}
I really suggest you to read the following link on importing-and-displaying-large-data-sets-in-core-data by Marcus Zarra.
When you deal with threads, each thread you create needs to have its own context as written in the link which jrturton provided. Then if you want to merge changes between a main context (the one you created in the main thread) and the other context (the one you use in the context), you need to listen for NSManagedObjectContextDidSaveNotification
in the main thread
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextHasChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
and do the merge like
- (void)contextHasChanged:(NSNotification*)notification
{
if ([notification object] == [self mainObjectContext]) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(contextHasChanged:) withObject:notification waitUntilDone:YES];
return;
}
[[self mainObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
The notification object contains the changes you made in the thread context.
Some notes
Threading is difficult to achieve. The link I provided uses an NSOperation
with its context. This is quite simple to set up but since iOS 5 there are some functionalities that make your life easier.
For example to create a context in a different thread you can do like the following:
// create a context with a private queue so access happens on a separate thread.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// insert this context into the current context hierarchy
context.parentContext = context;
// execute the block on the queue of the context
[context performBlock:^{
// do your stuff (e.g. a long import operation)
// save the context here
// with parent/child contexts, saving a context pushes the changes out of the current context
NSError* error = nil;
[context save:&error];
}];
In addition you can see the doc for UIManagedDocument
. This class integrates well for Core Data and allows you to avoid using the Core Data stack.
Hope it helps.
Your particular situation (downloading JSON in the background) is quite common. I have seen a number of implementations that make do without multiple contexts.
In many cases, the simplest and by far the robustest way is to have the objects that do the downloads notify the main thread via protocol or notification of a finished download. You then do the saving on the main thread.