ref – http://blog.chadwilken.com/core-data-concurrency/
http://stackoverflow.com/questions/30089401/core-data-should-i-be-fetching-objects-from-the-parent-context-or-does-the-chil
https://github.com/skandragon/CoreDataMultipleContexts
https://medium.com/bpxl-craft/thoughts-on-core-data-stack-configurations-b24b0ea275f3#.vml9s629s
In iOS 5, Apple introduced
- 1) parent/child contests – settings a context as a child to a parent context is that whenever the child saves, it doesn’t persist its changes to disk. It simply updates it own context via a NSManagedObjectContextDidSaveNotification message. Instead, it pushes its changes up to its parent so that the parent can save it.
- 2) concurrency queue that is tied to a context. It guarantees that if you execute your code block using that context, it will use execute your code block on its assigned queue
Thus, we can use parent/child contexts and concurrency queue to set up our Core Data Stack.
Say we create a parent context. It will act as the app’s Task Master. Its children will push their changes up so that the task master will and executes them in a background queue.
When creating this Task Master context, we init it with macro NSPrivateQueueConcurrencyType to let it know that the queue is a private one. Because its a private queue, the threads used to run the tasks in this private queue will be executed in the background.
1 2 3 4 5 6 7 8 9 10 11 12 |
@interface CoreDataStack : NSObject { } +(instancetype) defaultStack; //instantiations of self MUST accesses this class method. //no other instantiations of this method is possible. Thus we ensure 1 entry point. //background context @property (readonly, strong, nonatomic) NSManagedObjectContext * privateQueueManagedObjectContext; ... ... @end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
@interface CoreDataStack () @property (nonatomic, strong) NSHashTable * contextDirectory; @end @implementation CoreStackData @synthesize contextDirectory; //to keep storage of all the temporary contexts //private queue context does job in the background @synthesize privateQueueManagedObjectContext = _privateQueueManagedObjectContext; .... - (NSManagedObjectContext *)privateQueueManagedObjectContext { if (_privateQueueManagedObjectContext != nil) { return _privateQueueManagedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (!coordinator) { return nil; } _privateQueueManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType]; [_privateQueueManagedObjectContext setPersistentStoreCoordinator:coordinator]; _privateQueueManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; return _privateQueueManagedObjectContext; } ..... @end |
Now, let’s a child context. We we create a temporary MOC who’s parent context is our Task Master.
1 2 3 4 5 6 7 8 9 10 11 12 |
- (NSManagedObjectContext *)createBackgroundContext { NSManagedObjectContext * context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; context.parentContext = self.privateQueueManagedObjectContext; context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; //We use this contextDirectory to decide if we should merge the changes into the mainQueueManagedObjectContext //by ensuring that the context triggering the notification exists in the directory as used in handleMOCDidSaveNotification. [self.contextDirectory addObject:context]; //weak reference return context; } |
Notice its parentContext assigned to our Task Master.
This implies that whenever our temporary MOCs finish their tasks, (whether its getting data from the net or reading in loads of data from somewhere or whatever task that takes quite a bit of time) and that we call MOC’s save,
this does 2 things:
1) save sends the NSManagedObjectContextDidSaveNotification message, and thus our Notification center observes. We go to handleMOCDidSaveNotification method where we call a method for the parentContext (task master) to do the save.
2) The background context’s changes are pushed into its parent context (task master).
Here is the handler that handles all name:NSManagedObjectContextDidSaveNotification message triggered by our child temporary MOCs.
1 2 3 4 5 |
//Whenever the <notifiation> NSManagedObjectContextDidSaveNotification from <object> is sent, then we call the selector method [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMOCDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object: nil]; |
where handleMOCDidSaveNotification is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
- (void)handleMOCDidSaveNotification:(NSNotification *)notification { NSManagedObjectContext *context = [notification object]; NSLog(@"handleMOCDidSaveNotification - %p", context); //because we already said that if the main context sends a NSManagedObjectContextDidSaveNotification, then go to saveMasterContext, //when we implement handleMOCDidSaveNotification, we have to be careful to avoid the ui context. We ONLY want to take care of //worker context. If its not a worker context OR if its the main UI context, we simply return. if (![self.contextDirectory containsObject:context] || context == self.mainQueueManagedObjectContext) { NSLog(@"%p is not a temp worker OR its a main queue context! Let's not touch it.....", context); return; } NSLog(@"handleMOCDidSaveNotification - background temp worker %p trigged this method...", context); //it is a temp worker context...so we have the main main UI context, merge with its changes... //it merges in the changes to the main UI context inside a performBlockAndWait call, ensuring it happens on the correct queue. [self.mainQueueManagedObjectContext performBlockAndWait:^{ NSLog(@"handleMOCDidSaveNotification - merge changes to main UI context"); [self.mainQueueManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; //notice its just a merge. There is no save }]; NSLog(@"background MOC calls Parent private Context to save"); //Once this finishes we go ahead and call saveMasterContext which in fact persists the changes to disk using the privateQueueManagedObjectContext. [self saveMasterContext]; } //whenever a worker context saves, it first merges with the main UI context, then it comes here for //the private context to do its saves - (void)saveMasterContext { NSLog(@"saveMasterContext - PRIVATE QUEUE SAVING...."); [self.privateQueueManagedObjectContext performBlockAndWait:^{ NSManagedObjectContext * privateQueueMOC = self.privateQueueManagedObjectContext; if(privateQueueMOC != nil) { NSError *error = nil; if ([privateQueueMOC hasChanges] && ![privateQueueMOC save:&error]) //if the private MOC has changes and saved with error { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } else { NSLog(@"private MOC has changes AND saved with no problem"); } } }]; } |
In handleMOCDidSaveNotification, we ignore other context, we take care of these temporary MOCs only. We RETURN and do nothing iff the temp MOCs are NOT tracked in our NSHashTable directory, or if its the UI context.
If the temp MOCs ARE tracked, that means they are temp MOCs, and we go ahead merge our temp MOCs with the UI MOC. We merge our changes with the UI MOC, because the UI MOC is not a parent context of the temp MOCs. The UI MOC is a separate entity.
When a MOC saves, its changes are pushed into its parent context. These however are not immediately written to disk. We write to the disk, after the background MOC saves, sends the NSManagedObjectContextDidSaveNotification notification, we go into handleMOCDidSaveNotification, and in the method saveMasterContext. saveMasterContext saves our parent private context, thus making it write to disk, and if it takes a long, its ok because its a private background queue.
http://stackoverflow.com/questions/30089401/core-data-should-i-be-fetching-objects-from-the-parent-context-or-does-the-chil
In short,
To clear up the confusion: after creating the child context from a parent context, that child context has the same “state” as the parent. Only if the two contexts do different things (create, modify, delete objects) the content of the two contexts will diverge.
So for your setup, proceed as follows:
create the child context
do the work you want to do (modifying or creating objects from the downloaded data),
save the child context
At this stage, nothing is saved to the persistent store yet. With the child save, the changes are just “pushed up” to the parent context. You can now
save the parent context
to write the new data to the persistent store. Then
update your UI,
best via notifications (e.g. the NSManagedObjectContextDidSaveNotification).