0

Using RestKit, I am mapping then saving objects from JSON in a background thread with a dedicated context.

I believe I am facing the problem highlighted here: NSFetchedResultsController with predicate ignores changes merged from different NSManagedObjectContext It is also very well explained here: http://www.mlsite.net/blog/?p=518

But my issue is not with UPDATED objects, but INSERTED ones. In my main MOC, I can see that the objects have been inserted after mergeChangesFromContextDidSaveNotification, but their data is at fault. I guess that's the reason why the NSPredicate on my NSFetchedResultController does not work (it looks at a specific field). I tried the workaround from the other SO question but I could not get it to work.

[[NSNotificationCenter defaultCenter]
    addObserverForName:NSManagedObjectContextObjectsDidChangeNotification
    object:nil
    queue:nil
    usingBlock:^(NSNotification* note)
    {
        NSManagedObjectContext *moc = managedObjectStore.mainQueueManagedObjectContext;
        if (note.object != moc){
            // When I look at note.userInfo.description, I can see that the objects seem properly created and inserted, with all their data
            [moc performBlock:^(){
                [moc mergeChangesFromContextDidSaveNotification:note];
                }];
        }
        else {
            // This does get called, i.e. the main moc is indeed saved properly. BUT when I look at note.userInfo.description the objects' data is at fault. And my NSFetchedResultController does not pick up the insertion.
        }
    }
];

UPDATE: adding my FRC config:

- (NSFetchedResultsController *)fetchedResultsController {

    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

    NSFetchedResultsController* theFetchedResultsController;
    NSFetchRequest* fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"User"];
    fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"userID" ascending:YES]];
    theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];

    // Note: self.managedObjectContext is set to [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext

    self.fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;


    [_fetchedResultsController performFetch:NULL];

    return _fetchedResultsController;

}

UPDATE 2: here is what I do when I receive JSON data:

RKObjectManager *manager = [RKObjectManager sharedManager];
NSPredicate *pre = [NSPredicate predicateWithFormat:@"keyPath IN %@", @"users"];
RKEntityMapping *mapping = (RKEntityMapping *)[[[manager responseDescriptors] filteredArrayUsingPredicate:pre].firstObject mapping];
NSDictionary *mappingsDictionary = @{ [NSNull null]: mapping };

if (!_backgroundMOC){ // Create a MOC for background mapping
    NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    context.persistentStoreCoordinator = [RKManagedObjectStore defaultStore].persistentStoreCoordinator;
    _backgroundMOC = context;
}


RKManagedObjectStore *store = [RKManagedObjectStore defaultStore];
RKManagedObjectMappingOperationDataSource *mappingDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:_backgroundMOC cache:store.managedObjectCache];

RKMapperOperation *mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:(id)jSONData  mappingsDictionary:mappingsDictionary];
mapperOperation.mappingOperationDataSource = mappingDataSource;
mapperOperation.delegate = self;
[manager.operationQueue addOperation:mapperOperation];

And:

- (void)mapperDidFinishMapping:(RKMapperOperation *)mapper {

    NSError *error;
    [((RKManagedObjectMappingOperationDataSource *)[mapper mappingOperationDataSource]).managedObjectContext save:&error];

}

Is there a known workaround for this? Or am I missing something?

Community
  • 1
  • 1
PJC
  • 997
  • 7
  • 21
  • Show the code for your FRC configuration. – Wain Dec 16 '13 at 13:14
  • @Wain Done. I should add one thing. I am "polling" the backend. It sends a first batch of Users (say 30 users), then a second one (say another 20) etc. After the second batch is loaded, `[self.fetchedResultsController fetchedObjects]` return the first 30 records, and so on. Basically the last set of data from the server is always ignored. It only gets fetched after the next load... – PJC Dec 16 '13 at 13:39
  • Ok, the FRC setup looks sensible (though you should always check for errors returned). You have implemented the delegate methods? And you're making the requests using `RKObjectManager`? – Wain Dec 16 '13 at 13:55
  • @Wain Thanks a million for helping me on this. I am really struggling. I have implemented `- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller` and it is in this function that I can see the number of fetched objects does not match what I get from the backend. I have also updated the question with details on how I map the JSON. – PJC Dec 16 '13 at 15:05
  • Looks sensible. In the notification block, did you try firing access changes for the key in your predicate (though you don't show a predicate in your code above). – Wain Dec 16 '13 at 15:30
  • Maybe that's where I am doing something wrong. When I get a new JSON payload, I extract the IDs of the users from the JSON itself, I update the FRC's `fetchRequest` with a new predicate (only fetch users whose IDs are in the set extracted above), I call performFetch on the FRC, then I send the JSON payload for mapping in the background. – PJC Dec 16 '13 at 15:48
  • 1
    I would expect it to work. Is there a reason you don't use `RKObjectManager` to get the JSON, map it and give you the result - then change the fetch request in the success block? – Wain Dec 16 '13 at 15:56
  • Thanks a million. You put me in the right direction and I found my specific problem. I was doing `[manager.operationQueue addOperation:mapperOperation];` and use `- (void)mapperDidFinishMapping:(RKMapperOperation *)mapper`. In this latter method, I was updating my predicate and calling `reloadData`. But this was happening in the background thread as well! I just made sure the predicate update and reloadData calls happen in the main thread and everything works. I think it is too complicated though. I will try your method instead. – PJC Dec 16 '13 at 16:22
  • Your current setup does definitely seem complex ;-) – Wain Dec 16 '13 at 16:23
  • Explain what "their data is at fault" means. What is actually incorrect in that situation? – Tom Harrington Dec 16 '13 at 17:38
  • @TomHarrington sorry the question has become a bit unclear with lots of comments. This was not the issue, you are right. The issue was I was updating the FRC's fetchRequest in a background thread instead of the main one. – PJC Dec 16 '13 at 18:14

0 Answers0