Reputation: 15748
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.
Upvotes: 1
Views: 1628
Reputation: 21
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.
Upvotes: 1
Reputation: 94
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]
Upvotes: 3