Search code examples
iosobjective-ccore-datansmanagedobjectcontext

Core Data pulling changes


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:

  1. How is it possible that managedObjectContext doesn't see changes unless I change fetchRequest and then fetch again first request?

  2. Is reset on managedObjectContext the only way to get correct data?

Thanks.


Solution

  • 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);
            }
        }];
    }