1
votes

I have a catalogue application in iOS 5 that is downloading data from XML and displaying them in UITableView using table view controllers with NSFetchedResultsController. Data are stored in Core Data UIManagedDocument. Because I don't want to block the main queue when downloading and importing data, I have created a background queue for downloading data and new child NSManagedObjectContext with NSPrivateQueueConcurrencyType for importing the data with document.managedObjectContext as a parent. When I finish importing data I -save: changes in the child context and the changes get propagated to parent context. When browsing the catalogue I import additional data when needed. Everything is working fine until the UIManagedDocument gets auto-saved.

I have turned on core data SQL debugging with -com.apple.CoreData.SQLDebug 1 to see when the document is auto-saved.

After document's auto-save objects with duplicate IDs are being created in document.managedObjectContext (all my entities id database have unique id parameter).

What am I doing wrong?


I have created a simple sample code to reproduce the problem.
Here is the code: http://dl.dropbox.com/u/20987346/ViewController.m
Here is the complete Xcode project: http://dl.dropbox.com/u/20987346/CoreDataTest.zip

Below is the method that is doing the import in the background.

- (void)backgroundImport
{
    static int counter;

    NSManagedObjectContext *backgroundContext;
    backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    backgroundContext.parentContext = self.document.managedObjectContext;

    [backgroundContext performBlock:^{
        NSManagedObject *entity;

        for (int i = 0; i < 2; i++) {
            entity = [self entityWithID:[NSNumber numberWithInt:arc4random() % 20 + 1]
                 inManagedObjectContext:backgroundContext];
            [entity setValue:[NSString stringWithFormat:@"A Name %d", ++counter] forKey:@"name"];
        }

        [self dumpEntitiesInManagedObjectContext:backgroundContext];

        NSError *error;
        [backgroundContext save:&error];
        if (error) NSLog(@"%@ (%@)", [error localizedDescription], [error localizedFailureReason]);

        [backgroundContext.parentContext performBlock:^{
            [self dumpEntitiesInManagedObjectContext:backgroundContext.parentContext];
        }];
    }];
}

The method imports two entities. The -entityWithID: fetches an entity with the specified ID attribute and if it does not exist it creates one using NSEntityDescription -insertNewObjectForEntityForName:. The -dumpEntitiesInManagedObjectContext: dumps all entities to log (once in the import context, once in the document's context).

The problem is that when the document is auto-saved and some additional importing is done, I get the following in the log:

[1140b] Entities: 10             [fb03] Entities: 11
[1140b]   2: A Name 1            [fb03]   2: A Name 1
[1140b]   3: A Name 4            [fb03]   3: A Name 4
[1140b]   4: A Name 8            [fb03]   4: A Name 8
[1140b]   5: A Name 12           [fb03]   5: A Name 12
[1140b]   6: A Name 10           [fb03]   6: A Name 10
[1140b]   8: A Name 6            [fb03]   8: A Name 6
[1140b] **12: A Name 11**        [fb03] **12: A Name 11**
[1140b]   13: A Name 9           [fb03] **12: A Name 5**
[1140b]   17: A Name 3           [fb03]   13: A Name 9
[1140b]   18: A Name 2           [fb03]   17: A Name 3
                                 [fb03]   18: A Name 2

The import context has 10 entities, but the main context has 11 entities and entity with ID 12 is a duplicate. Seems that the old object has not been modified in parent context but added instead.

2

2 Answers

3
votes

I'm still in the thick of all this kind of stuff (Core Data & UIManagedDocument working together) but I think this question might address your situation: Core Data managed object does not see related objects until restart Simulator

It involves forcing temporary ids to be permanent before the "normal" flow using: [context obtainPermanentIDsForObjects:[inserts allObjects] error:&error]

1
votes

I came across this problem and made a little category on NSEntityDescription to solve the issue:

@implementation NSEntityDescription (PermanentID)
+ (id)insertNewPermanentObjectForEntityForName:(NSString *)entityName
                        inManagedObjectContext:(NSManagedObjectContext *)context
{
    id object = [self insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
    NSError *error;
    if (![context obtainPermanentIDsForObjects:[NSArray arrayWithObject:object] error:&error]) {
        NSLog(@"Permanent ID not given");
    }
    if (error) {
        NSLog(@"%@", error);
    }
    return object;
}

There's more about it here on my site, but I've reproduced the category here so you don't have to click through.