Parent-Child Contexts
An important point that you need to get a good grip on is that contexts can be chained such that one is the child of another.
CoreDataStack.h
1 2 |
@property (readonly, strong, nonatomic) NSManagedObjectContext *privateQueueContext; @property (readonly, strong, nonatomic) NSManagedObjectContext *parentContext; |
CoreDataStack.m
1 2 |
@synthesize privateQueueContext = _privateQueueContext; @synthesize parentContext = _parentContext; |
We create a private (child) and parent context.
The parent is what connects to the Persistent Store Coordinator.
The child is connected to the parent context through the parentContext property.
The child DOES NOT connect to the PSC.
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 |
- (NSManagedObjectContext *)parentContext { // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) if (_parentContext != nil) { return _parentContext; } _parentContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; NSLog(@"-- Parent Context allocated -- "); NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (!coordinator) { return nil; } [_parentContext setPersistentStoreCoordinator:coordinator]; NSLog(@"Parent Context connect to the PSC"); NSLog(@"Parent Context created "); return _parentContext; } - (NSManagedObjectContext *)privateQueueContext { // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) if (_privateQueueContext != nil) { return _privateQueueContext; } _privateQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; NSLog(@"private context allocated"); NSLog(@"Connect it to the Parent Context"); _privateQueueContext.parentContext = self.parentContext; NSLog(@"Pvt Context does not connect to PSC"); NSLog(@"PrivateQueueContext CREATED "); return _privateQueueContext; } |
If and when you do this, you have to realize that:
- Changes saved in a child context are pushed to the parent, not to the persistent store.
- The parent context behaves as the child’s persistent store.
- Changes saved in a parent are not pushed down to the child.
- Changes saved in a sibling are not pushed to other siblings.
This means that changes only propagate one level up in a context hierarchy. If the context you are saving is several levels away from the persistent store, you will need to save each parent context in the hierarchy until you reach the context that saves to the persistent store to actually persist your changes.
Demo – Changes saved in an child context are pushed to the parent
First click the Show Private Context Button.
output:
(PRIVATE CONTEXT) – no people found
Do the same for “Show Parent Context” and you’ll get the same output.
Hence, we know that both child/parent contexts do not have any data so far.
Okay, Let’s first add some objects.
We first put a block task onto the private context. Its basically adding three Person objects.
Once those objects are initialized, we tell the context to save.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[self.privateQueueContext performBlock:^{ for(int i = 0; i < 3; i++) { //will return an object from the heap, so we can store it //insert new entry into core data environment Person * aPerson = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.privateQueueContext]; NSTimeInterval today = [[NSDate date] timeIntervalSince1970]; aPerson.firstName = [NSString stringWithFormat:@"remark: %@", [NSString stringWithFormat:@"%f", today]]; aPerson.lastName = [NSString stringWithFormat:@"%f", today]; NSLog(@"added %@ %@", aPerson.firstName, aPerson.lastName); } [self privateQueueContextOneSave]; }]; |
Press the ‘add bulk’ button do add the objects.
output:
——– ADD BULK PEOPLE ———–
added remark: 1472782237.562826 1472782237.562826
added remark: 1472782237.563077 1472782237.563077
added remark: 1472782237.563318 1472782237.563318
PRIVATE CONTEXT save success!
Now, if you click the “Show Pvt Context” button and “Show Parent Context” button, you’ll see that they both show data. This is because when the private context saves its data, it pushes it up to the parent automatically.
output:
(PRIVATE CONTEXT) —— RESULTS ————
There are 3 number of entries so far
0 —————-
firstName = remark: 1472782941.371055
lastName = 1472782941.371055
object address is: 0x7f835bf95af0
…
…
(PARENT CONTEXT) —— RESULTS ————
There are 3 number of entries so far
0 —————-
firstName = remark: 1472782941.371055
lastName = 1472782941.371055
object address is: 0x7f835bf11150
….
…
What happens to the parent context if child does not save?
Let’s edit the first object by pressing the “Pvt Context, [0] to Ricky” button.
We put a task block onto the private context. It gets the first object, and changes
the first name to “Ricky”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[self.privateQueueContext performBlock:^{ NSLog(@"PRIVATE CONTEXT -- CHANGE FIRST PERSON TO RICKY... ------------"); //Get all the projects in the data store NSFetchRequest *request = [[NSFetchRequest alloc] init]; NSEntityDescription * entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:[self privateQueueContext]]; request.entity = entity; NSArray *listOfPeople = [[self privateQueueContext] executeFetchRequest:request error:nil]; if([listOfPeople count]>0) { Person * temp = [listOfPeople objectAtIndex:0]; temp.firstName = @"RICKY"; } else { NSLog(@"sorry, no people found in database"); } }]; |
output:
PRIVATE CONTEXT — CHANGE FIRST PERSON TO RICKY… ————
Then have the private context display its results. You will see that Ricky appears.
output:
(PRIVATE CONTEXT) —— RESULTS ————
There are 3 number of entries so far
…
0 —————-
firstName = RICKY
lastName = 1472783378.062207
object address is: 0x7f8bc161d400
But if you display the PARENT CONTEXT, you will not see it. The reason why is because you need to save the private context in order
to propagate the changes up the parent.
Try clicking on the “Save Pvt context” button. You’ll see this:
PRIVATE CONTEXT save success!
Then click the “Show Parent Context” button and you’ll see Ricky in the results:
output:
(PARENT CONTEXT) —— RESULTS ————
There are 3 number of entries so far
…
0 —————-
firstName = RICKY
lastName = 1472783378.062207
object address is: 0x7f8bc161b390
Now, press the “Main Context Display All”, you’ll see that its empty:
That’s because each save only propagate up one level. Thus, in order for the changes to be saved into the PSC, 2 points must hold:
1) The context must be connected to the PSC
2) The context’s parent context must be nil.
Our parent context satisfies both.
Click on “Main Context Display All”. You’ll still get:
There are no people in the data store yet. We need to make sure each context saves to propagate the changes up.
Now, save the parent context by clicking on “Save Parent Context”.
1 2 3 4 5 6 7 8 9 10 11 12 |
[self.parentContext performBlock:^{ NSError *error = nil; if ([self.parentContext hasChanges] && ![self.parentContext save:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } else { //success! NSLog(@"PARENT CONTEXT save success!"); } }]; |
Due to the parentContext’s PSC is set, and that its parent context is nil, its saves will reflect to the PSC.
Thus, whatever context that’s connected to the PSC will be able to see the results.
Click on “Main Context Display All” and you’ll see that our main context now has the results:
(MAIN CONTEXT) —— RESULTS ————
There are 3 number of entries so far
…
firstName = RICKY
lastName = 1472784795.060829
object address is: 0x7fac7acbbeb0