Search code examples
cocoa-touchcore-dataios6nsmanagedobjectcontext

Core Data: NSManagedObjectContext Not Saved / Fetch Request Fails Until App Quits & Relaunches


I've got a Core Data backend that's exhibiting some strange behavior. The first time I run my app (in the simulator) my NSFetchRequest will never find matches even when given identical items multiple times in a row (it will return 'no results' each request then proceed to insert duplicate info into the database - which I can see happen because I have a tableview tied to the database).

If I "quit" the app by pressing the home button then re-open the app. It starts behaving as expected (returning results where appropriate). I can also delete the app and then run it again from Xcode to reset this process back to the beginning. It seems it doesn't save the database until the app closes even though I'm calling save on the NSManagedObjectContext (which is returning true).

What's going on here? How do I make this work as expected? I'm thinking I'm just not saving my changes for the NSManagedObjectContext but how do I do that?

Here's the function which fetches/returns object out of my NSManagedObjectContext:

+ (Mark *)markWithTWLInfo:(NSDictionary *)markDictionary
   inManagedObjectContext:(NSManagedObjectContext *)context
{
    Mark *mark = nil;

    NSLog(@"Starting Operation for Key: %@", [markDictionary[JS_MARK_ID] description]);

    // Build a fetch request to see if we can find this Mark in the database.
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Mark"];
    request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES]];
    request.predicate = [NSPredicate predicateWithFormat:@"idUnique = %@", [markDictionary[JS_MARK_ID] description]];

    // Execute the fetch

    NSError *error = nil;
    NSArray *matches = [context executeFetchRequest:request error:&error];

    // Check what happened in the fetch

    if (!matches || ([matches count] > 1) || error ) {  // nil means fetch failed; more than one impossible (unique!)
        // handle error
        if (error) {
            NSLog(@"Fetch error: %@", [error description]);
        } else {
            NSLog(@"Found No/Multiple matches for key: %@", [markDictionary[JS_MARK_ID] description]);
        }
    } else if (![matches count]) { // none found, so let's create a mark

        NSLog(@"Inserting: %@", [markDictionary[JS_MARK_ID] description]);

        mark = [NSEntityDescription insertNewObjectForEntityForName:@"Mark" inManagedObjectContext:context];
        mark.idUnique   = [NSNumber numberWithInt:[markDictionary[JS_MARK_ID] intValue]];

        //Save the changes; this returns True
        if ([context save:&error]) {
            NSLog(@"Saved is true");
        } else {
            NSLog(@"Saved is false");
        }

        if (error) {
            NSLog(@"Save error: %@", [error description]);
        }
    } else { // found the mark, just return it from the list of matches (which there will only be one of)
        NSLog(@"Found existing object for key: %@", [markDictionary[JS_MARK_ID] description]);
        mark = [matches lastObject];
    }
    return mark;
}

I call this function like so for every mark that I want to insert:

for (NSDictionary *mark in results) {
    if (DEMO_LOGGING) NSLog(@"Inserting: %@",[mark objectForKey:@"Mark"]);
    [self.managedObjectContext performBlock:^{
        [Mark markWithTWLInfo:[mark objectForKey:@"Mark"] inManagedObjectContext:self.managedObjectContext];
    }];
}

Here's what I see in the log when I run into this problem:

-Start the app with a fresh database:

2013-05-05 16:45:08.105 ToWatchList[10155:c07] Starting Operation for Key: 731
2013-05-05 16:45:08.106 ToWatchList[10155:c07] Inserting: 731
2013-05-05 16:45:08.111 ToWatchList[10155:c07] Saved is true
2013-05-05 16:45:10.651 ToWatchList[10155:c07] Starting Operation for Key: 731
2013-05-05 16:45:10.652 ToWatchList[10155:c07] Inserting: 731
2013-05-05 16:45:10.654 ToWatchList[10155:c07] Saved is true

-quit and relaunch the program here

2013-05-05 16:45:29.816 ToWatchList[10155:c07] Starting Operation for Key: 731
2013-05-05 16:45:29.817 ToWatchList[10155:c07] Found No/Multiple matches for key: 731

-Now NSFetchRequest returns my 2 previous entries as expected, but it should have seen the first one when it tried to insert the second.


Solution

  • My guess is that you have a parent context to your self.managedObjectContext.

    If this is the case, in order to ensure the uniqueness of the property, you will have to save all the way to the store (recursively save until no parentContext exists).

    Note that you must wait for the save to complete on all parent contexts before you can ensure the uniqueness.

    replace your [context save:&error] with:

    NSManagedObjectContext* c = context;
    __block BOOL success = YES;
    while (c && success) {
        [c performBlockAndWait:^{
            success = [c save:&error];
            //handle save success/failure
        }];
        c = c.parentContext;
    }