Core Data: Do child contexts ever get permanent objectIDs for newly inserted objects?
Asked Answered
K

3

30

I have an app with two managed object contexts setup like this:

  • Parent Context: NSPrivateQueueConcurrencyType, linked to the persistent store.
  • Main Context: NSMainQueueConcurrencyType, child of Parent Context.

When insert a new managed object to the main context, I save the main context and then the parent context like this:

[context performBlockAndWait:^{
    NSError * error = nil;
    if (![context save: &error]) {
        NSLog(@"Core Data save error %@, %@", error, [error userInfo]);
    }
}];

[parentContext performBlock:^{
    NSError *error = nil;
    BOOL result = [parentContext save: &error];
    if ( ! result ) {
        NSLog( @"Core Data save error in parent context %@, %@", error, [error userInfo] );
    }
}];

My understanding is that when the manage object is first created, it has a temporary objectID. Then the main context is saved and this object, with its temporary ID, gets to the parent context. Then the parent context is saved. When this last context is saved, the temporary objectID in the parent context gets transformed into a permanent objectID.

So:

  • Does the permanent object ID ever get propagated automatically back to the main (child) context?
  • When I force to get the object permanent ID with [NSManagedObjectContext obtainPermanentIDsForObjects:error:], then background the app, reactivate it, reload, get the object using main context's objectWithID:, and access a property, I get

    "CoreData could not fulfill a fault for ...".

What is wrong with this approach?
Kellie answered 16/8, 2012 at 15:10 Comment(1)
Jorge: I'm getting a CoreData could not fulfill a fault when obtaining a permanent ID in a child context & saving the ManagedObject. Did you found out what was the reason ?. ThanksMonk
E
43

It is a known bug, hopefully fixed soon, but in general, obtaining a permanent ID is sufficient, provided you do so before you save the data in the first child, and you only include the inserted objects:

[moc obtainPermanentIDsForObjects:moc.insertedObjects.allObjects error:&error]

In some complex cases, it is better to get a permanent ID as soon as you create the instance, especially if you have complex relationships.

How and when are you calling obtainPermanentIDsForObjects?

I do not follow the part about the app crashing. Maybe a better explanation would help.

Emilie answered 16/8, 2012 at 23:39 Comment(3)
There is nothing in moc.insertedObjects set. I guess that gets cleared after the save? What I am doing is saving the user selection of documents when the app is backgrounded. The app can update those while it is in the background. When it enters the foreground the context is recreated and selection is restored using the object IDs. Before going to the background, however, the newly created documents, do have temporary IDs even after the context save. If I force getting the permanent IDs for those, when the app gets reactivated, it cannot recreate the managed objects.Kellie
Yes, insertedObjects only holds objects that have been inserted and not saved. Even after you give them permanent IDs, they will remain in insertedObjects until they are saved. Try doing the obtain on your child context before saving. It should grab the IDs, then propagate the save based on them. Make sure you save fully through all contexts to the database.Emilie
I added a check before my generic managed object save (I'm using SSManagedObject and subclassed it to customize the save method). Can't believe this is still and issue and is undocumented.Sharitasharity
D
11

As Jody said above, when creating a new NSManagedObject in a background thread using a child ManagedObjectContext you must force the creation of a permanent ID by doing the following BEFORE you save:

NSError *error = nil;

[threadedMOC obtainPermanentIDsForObjects:threadedMOC.insertedObjects.allObjects error:&error];

BOOL success = [threadedMOC save:&error];

IMHO, it's not really intuitive to do it this way - after all, you're asking for a permanent ID BEFORE you save! But this is the way it seems to work. If you ask for the permanent ID after the save, then the ID will still be temporary. From the Apple Docs, you can actually use the following to determine if the object's ID is temporary:

BOOL isTemporary = [[managedObject objectID] isTemporaryID];
Darby answered 8/10, 2013 at 12:1 Comment(0)
A
2

Problem still exists in iOS 8.3 Solution in Swift :

func saveContext(context: NSManagedObjectContext?){
   NSOperationQueue.mainQueue().addOperationWithBlock(){
    if let moc = context {
        var error : NSError? = nil
        if !moc.obtainPermanentIDsForObjects(Array(moc.insertedObjects), error: &error){
            println("\(__FUNCTION__)\n \(error?.localizedDescription)\n \(error?.userInfo)")
        }
        if moc.hasChanges && !moc.save(&error){
            println("\(__FUNCTION__)\n \(error?.localizedDescription)\n \(error?.userInfo)")
        }
    }
 }
}

func saveBackgroundContext(){
    saveContext(self.defaultContext)

    privateContext?.performBlock{
        var error : NSError? = nil
        if let context = self.privateContext {

            if context.hasChanges && !context.save(&error){
                println("\(__FUNCTION__)\n \(error?.localizedDescription)\n \(error?.userInfo)")
            }else {
                println("saved private context to disk")
            }
        }
    }
}

Where:

  • defaultContext has concurrencyType .MainQueueConcurrencyType
  • privateContext has concurrencyType .PrivateQueueConcurrencyType
Antinode answered 22/4, 2015 at 20:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.