2

I am using MagicalRecord, but not sure if this is a bug in MagicalRecord, or CoreData.

I create new entities in my UI thread when a user clicks the new button and it gets saved to the default context:

- (IBAction) saveToCoreData:(UIButton *)sender {
    NSManagedObjectContext *context = [NSManagedObjectContext MR_defaultContext];
    Report *report = [Report MR_createInContext:context];
    report.dirty = [NSNumber numberWithBool:YES];
    report.timestamp = [NSDate date];

    Document *document = [Document MR_createInContext:context];
    document.dirty = [NSNumber numberWithBool:YES];
    document.timestamp = [NSDate date];
    document.report = report;

    NSLog(@"Saving.................................");
    [context MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
        NSLog(@"Saved report and document to PS");
    }];
}

Notice I have a Report entity with a child Document attached

Then I have a background thread that is only responsible to listen for changes, if a Report changes, dirty == YES then it needs to get submitted to the server.

I set up a NSFetchedResultsController on a background thread to listen for changes so I know when save to server.

// This should get me a new background context, use it in FRC below
    self.fetchedResultsController = [Report MR_fetchAllSortedBy:@"timestamp"
                                                      ascending:NO
                                                  withPredicate:[NSPredicate predicateWithFormat:@"dirty == YES"]
                                                        groupBy:nil
                                                       delegate:self
                                                      inContext:_managedObjectContext];

Then I implement the method to listen for changes:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id) anObject atIndexPath:(NSIndexPath *) indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *) newIndexPath {
    switch(type) {

        case NSFetchedResultsChangeInsert:
            NSLog(@"report inserted");
            [self pushReport:anObject];
            break;
        case NSFetchedResultsChangeUpdate:
            NSLog(@"report updated");
            [self pushReport:anObject];
            break;
    }
}

and pushing it to the server:

- (void) pushReport:(Report *) report {
    __weak ReportPushService *weakSelf = self;
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    [manager GET:@"http://echo.jsontest.com/" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
        [weakSelf.managedObjectContext performBlockAndWait:^{
            report.remoteId = @"12345"; // fake id from server
            report.dirty = [NSNumber numberWithBool:NO];

            [weakSelf.managedObjectContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
                NSLog(@"Saved server info to Report");
            }];
        }];
     } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
         NSLog(@"Error: %@", error);
     }];
}

The FRC works like a charm and when the UI thread adds a new report with dirty == YES the FRC picks it up and submits it to the server.

Here is where I run into problems. After the report is saved to the server, the server return an ID for that report. I set that as report.remoteId.

I have a FRC in another class that is responsible for saving documents to the server. It will check the documents dirty == YES and that it parent (Report) remoteId != nil, ie:

    self.fetchedResultsController = [Document MR_fetchAllSortedBy:@"timestamp"
                                                      ascending:NO
                                                  withPredicate:[NSPredicate predicateWithFormat:@"report.remoteId != nil && dirty == YES"]
                                                        groupBy:nil
                                                       delegate:self
                                                      inContext:_managedObjectContext];

It uses the same background MOC as the FRC controller in the Report "push" class.

I then listen for changes on that FRC:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id) anObject atIndexPath:(NSIndexPath *) indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *) newIndexPath {
    switch(type) {
        case NSFetchedResultsChangeInsert:
            NSLog(@"document inserted");
            [self pushDocument:anObject];
            break;
        case NSFetchedResultsChangeUpdate:
            NSLog(@"document updated");
            [self pushDocument:anObject];
            break;
    }
}

However his FRC never sees any changes, even though I have verified that I have a report with remoteId != nil and a (child) document with dirty == YES.

Why is the Documents FRC not seeing objects that match that predicate?

lostintranslation
  • 23,756
  • 50
  • 159
  • 262
  • 1
    do you observe `NSManagedObjectContextDidSaveNotification` of MOC in UI thread and merge the change into MOC in background thread? – bluedome Dec 17 '14 at 04:49
  • You need to describe how you setup the MOCs and how you manage the threads (are you always on the right thread for the MOC and are the changes merged across your MOCs) – Wain Dec 31 '14 at 11:18
  • Does the misbehaving NSFetchedResultsController ever perform a fetch? Is it on the correct context? Are all of the required delegate methods implemented and the delegate of the NSFetchedResultsController set? – quellish Jan 01 '15 at 02:24

1 Answers1

1

The problem / limitation with FRC is that it only contains the entities for which it was setup. In this case:

self.fetchedResultsController = [Document MR_fetchAllSortedBy:@"timestamp"
                                                      ascending:NO
                                                  withPredicate:[NSPredicate predicateWithFormat:@"report.remoteId != nil && dirty == YES"]
                                                        groupBy:nil
                                                       delegate:self
                                                      inContext:_managedObjectContext];

the FRC will only contain Document entities. The initial fetch will work on the report relation. But if a report changes the FRC will not be updated, even though the predicate matches.

http://www.mlsite.net/blog/?p=825

and here

Changing a managed object property doesn't trigger NSFetchedResultsController to update the table view

Community
  • 1
  • 1
lostintranslation
  • 23,756
  • 50
  • 159
  • 262