With Core Data, it seems the best way to learn is to compare against what has worked. I'll post some code that deals with situations similar to yours.
First: Is this the right way to set up two contexts for my situation?
This is what I have in my AppDelegate right now:
- (NSManagedObjectContext *)auxiliaryManagedObjectContext {
NSManagedObjectContext *managedObjectContext = nil;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:coordinator];
[managedObjectContext setUndoManager:nil];
}
return managedObjectContext;
}
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
Second: Would this be the proper way to call the individual sync and
load methods?
This really depends on what is happening in these methods. When you are working with a context in the background, you should send setup a notification that will trigger when a change is made. In this method, you then merge the changes from the background context into the UI context:
dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL);
dispatch_async(background, ^{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *backgroundManagedObjectContext = [appDelegate auxiliaryManagedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(mergeContexts:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundManagedObjectContext];
NSManagedObject *threadSafeManagedObject =
[backgroundManagedObjectContext objectWithID:self.currentManagedObject.objectID];
NSManagedObject *insertedThreadSafeManagedObject = [NSEntityDescription insertNewObjectForEntityForName:@"Entity" inManagedObjectContext:backgroundManagedObjectContext];
NSError *error;
// Will call the mergeContexts: selector registered above
if(![backgroundManagedObjectContext save:&error]) {
NSLog(@"Error! %@", error);
}
});
- (void)mergeContexts:(NSNotification *)notification {
SEL selector = @selector(mergeChangesFromContextDidSaveNotification:);
[self.managedObjectContext performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
}
In the mergeContexts: method, the background context is merged with your UI context. It is very important that a context that starts in the background, stays in the background and vice-versa. Additionally, managed objects are NOT thread safe, so to use one from the UI thread in the background thread, you must transfer it to the background context view the objectID as in the example above.
Third: My Core Data is centered around one entity (User) which all
other entities are connected to. Do I have to create a local variable
and fetch it every time I want to use it, or is there a better way so
the NSManagedObject is not shared across the two threads?
The description above should have answered this question.
Fourth: If I save the parent background context, will the child
context (UI) automatically be updated or is there a specific method I
need to call?
That would be the mergeContexts: method in the above example. Let me know if this makes sense.
EDIT 1: initWithConcurrencyType
As you mentioned, there are the NSMainQueueConcurrencyType and NSPrivateQueueConcurrencyType used during initialization of managed object contexts that handle this back and forth exchange between background and UI contexts.
The AppDelegate initializers would be practically the same. The only difference would be the init statements:
// Background context
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// Main thread context
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
You would then grab the background context–in this situation from your AppDelegate–and use the background thread to begin the work and finish the work on the main thread:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *backgroundManagedObjectContext = [appDelegate auxiliaryManagedObjectContext];
[backgroundManagedObjectContext performBlock:^{
// Do work
[self.managedObjectContext performBlock:^{
// merge work into main context
}];
}];
It is important to state the same thread safety rules apply (e.g. background objects must be transferred to the main thread view its objectID). To pass the work from your background thread to the main thread, you should call the main thread context's performBlock inside the background context's perform block. Though the convenience of merging contexts still lies within the notification arrangement regardless of how you initialize your contexts.
EDIT 2: Child/Parent Contexts
Here is a parent/child example from Correct implementation of parent/child NSManagedObjectContext:
- (void)saveContexts {
[childContext performBlock:^{
NSError *childError = nil;
if ([childContext save:&childError) {
[parentContext performBlock:^{
NSError *parentError = nil;
if (![parentContext save:&parentError]) {
NSLog(@"Error saving parent");
}
}];
} else {
NSLog(@"Error saving child");
}
}];
}
In our case, when initializing the child context, set its parent to your UI context:
[backgroundManagedObjectContext setParentContext:_managedObjectContext];