Ok, here's the problem: I use managedObjectContext
hierarchy. I have a root managedObjectContext
that is singleton so I can share same context and in the same time I can have child context for separating saving.
_managedObjectContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
_managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
And this is how I create child managedObjectContext
which I have one for each viewController
NSManagedObjectContext* childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
childContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
childContext.parentContext = _managedObjectContext;
For everyone who thinks that it's enough to save just child managedObjectContext
, save works only one ancestor up, you have to do it until parent is nil
. I have just parent and child so this is my code for saving data to store
- (void)saveChildContext:(NSManagedObjectContext*)childContext
{
[childContext performBlockAndWait:^{
NSError* error;
[childContext save:&error];
[_managedObjectContext performBlock:^{
NSError* parentError;
[_managedObjectContext save:&parentError];
}];
}];
}
I have a viewController
that fetches data for a tableView
.
arrObjects = [dbClient getEntitiesOfType:@"Object" predicate:@"%K == nil || %K.length < 1" predicateArray:[NSArray arrayWithObjects:@"someRelationship.property", @"someRelationship.otherProperty", nil] childContext:childContext];
And this is behind:
- (NSArray*)getEntitiesOfType:(NSString *)entityType predicate:(NSString*)predicateString predicateArray:(NSArray*)predicateArray childContext(NSManagedObjectContext*)context
{
NSManagedObjectContext* managedObjectContext;
if (context != nil)
managedObjectContext = context;
else
managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
NSEntityDescription* entityDescription = [NSEntityDescription entityForName:entityType inManagedObjectContext:managedObjectContext];
NSPredicate* predicate = [NSPredicate predicateWithFormat:predicateString argumentArray:predicateArray];
NSFetchRequest* request = [[NSFetchRequest alloc] init];
request.entity = entityDescription;
request.predicate = predicate;
NSError* error = [[NSError alloc] init];
return [managedObjectContext executeFetchRequest:request error:&error];
}
else
return nil;
}
When going to details of one of fetched results, I make changes, save context, which propagates changes to parent and then to store. I pass the object and in details I work with object's managedObjectContext
. After saving data to store, I pop my viewController
and everything is fine.
If I choose to do something with that object on another viewController
, I save context because I made some changes, pass objectId
, I fetch that object with another managedObjectContext
, but that has same parent, make changes and save it to parent and then to store. Everything is stored nicely. Oh, here I use NSNotification
because viewControllers
don't know about the other's existence.
[self.navigationController popViewControllerAnimated:NO];
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:currentTrip.objectID, @"ObjectId", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"tripRecording" object:nil userInfo:userInfo];
The problem is that when I get to first viewController
(tableViewController
), I fetch data on viewWillAppear
, and I don't get changes. On the other hand, if I press segment on same view, fetch another data and then get back to first segment, pressing it and fetching first set of data again, I get correct answers. That means that data got stored correctly, no matter which context I used.
My questions are:
How is it possible that managedObjectContext
doesn't see changes unless I change fetchRequest
and then fetch again first request?
Is reset
on managedObjectContext
the only way to get correct data?
Thanks.
Here is my setup using two different NSManagedObjectContext
:
Creating my master NSManagedObjectContext
, I never directly work off this context:
- (NSManagedObjectContext *)masterManagedObjectContext {
if (_masterManagedObjectContext != nil) {
return _masterManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_masterManagedObjectContext performBlockAndWait:^{
[_masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
}];
}
return _masterManagedObjectContext;
}
Creating a new NSManagedObjectContext
on the fly:
- (NSManagedObjectContext *)newManagedObjectContext {
NSManagedObjectContext *newContext = nil;
NSManagedObjectContext *masterContext = [self masterManagedObjectContext];
if (masterContext != nil) {
newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[newContext performBlockAndWait:^{
[newContext setParentContext:masterContext];
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(backgroundDidSaveNotification:)
name:NSManagedObjectContextDidSaveNotification
object:newContext];
}];
}
return newContext;
}
Method to listen for when the new NSManagedObjectContext
saves so it can merge into master:
- (void)backgroundDidSaveNotification:(NSNotification*)notificaton {
[self.masterManagedObjectContext mergeChangesFromContextDidSaveNotification:notificaton];
[self saveMasterContext];
}
For clarity my saveMasterContext
:
- (void)saveMasterContext {
[self.masterManagedObjectContext performBlockAndWait:^{
NSError *error = nil;
BOOL saved = [self.masterManagedObjectContext save:&error];
if (!saved) {
// do some real error handling
NSLog(@"Could not save master context due to %@", error);
}
}];
}