Proper implementation of parent / child NSManagedObjectContext

My application sometimes inserts objects into the context of a managed object, which need not be persisted. For example, when I run the add entity mod, I create a managed entity and assign it to a modal. If the user saves this modal mode, I keep the context. If it cancels, I delete the object and saving is not required.

Now I have introduced an import function that switches to my application (using a URL scheme) and adds an object. Since one of these modals may be open, it is unsafe to maintain context at this point. The temporary object created for the modal will be saved even if the user cancels, and there is no guarantee that the deletion (from the cancel operation) will be saved later - the user can exit the application.

Similarly, I cannot just save when my application terminates. If the modality is open at this point, the temporary object will not be saved correctly.

To solve this problem, I am trying to use a child context, as discussed here . After reading everything that I could find on SO, I had a few questions:

  • What type of concurrency should be used for each context? Remember that I am not doing this for performance / threading benefits. I know that I cannot use NSConfinementConcurrencyType for the main context if it has child contexts, but I'm not sure which of the other two options works best. As for the children's context, does it need to be matched? Or can I use confinement type here? I tried various combinations and everything seems to work fine, but I would like to know what suits my requirements.

  • (side issue) Why can I make this work if I use the iVar class? I thought that I should be able to declare a temporary context in the method where it was created, and then later refer to it using entity.managedObjectContext. But, unfortunately, by the time I come to him? This is fixed if I use iVar instead to store the link.

  • What is the correct way or propagation of changes in the main context? I saw various comments using different blocked implementations in each of the contexts. Does it depend on my type of concurrency? My current version is:

    //save the new entity in the temporary context NSError *error = nil; if (![myObject.managedObjectContext save:&error]) {NSLog(@"Error - unable to save new object in its (temporary) context");} //propogate the save to the main context [self.mainContext performBlock:^{ NSError *error2 = nil; if (![self.mainContext save:&error2]) {NSLog(@"Error - unable to merge new entity into main context");} }]; 
  • When my user saves, he sends a message to his delegate (my main view controller). The delegate is passed the object that was added, and it must find the same object in the main context. But when I look for it in the main context, it is not found. There is an object in the main context - I can register its data and confirm that it is - but the address is different? If this should happen (why?), How can I find the added object in the main context after saving?

Thank you for understanding. Sorry for the long, frequent question, but I thought that someone would probably address all these issues earlier.

+24
ios objective-c cocoa core-data nsmanagedobjectcontext
Jan 11 '13 at 18:20
source share
3 answers

The parent / child MOC model is a really powerful Core Data feature. This incredibly simplified the old concurrency issue we were dealing with. However, as you said, concurrency is not your problem. To answer your questions:

  • Traditionally, you use NSMainQueueConcurrencyType for NSManagedObjectContext associated with the main thread, and NSPrivateQueueConcurrencyType for child contexts. A child context does not have to match its parent element. NSConfinementConcurrencyType is what all NSManagedObjectContext get by default, unless you specify a type. This is basically "I will manage my flows for master data."
  • Without looking at the code, my guess will be the area in which you create the end of the child context, and is cleared.
  • When using the parent / child context template, you need to use block methods. The biggest advantage of using block methods is that the OS will process method calls for the correct threads. You can use performBlock for asynchronous execution or performBlockAndWait for synchronous execution.

You would use this, for example:

 - (void)saveContexts { [childContext performBlock:^{ NSError *childError = nil; if ([childContext save:&childError]) { [parentContext performBlock:^{ NSError *parentError = nil; if (![parentContext save:&parentError]) { NSLog(@"Error saving parent"); } }]; } else { NSLog(@"Error saving child"); } }]; } 

Now you need to keep in mind that changes made to the child context (for example, nested objects) will not be available to the parent context until you save. For a child context, the parent context is persistent storage. When you save, you transfer these changes to the parent, who can then save them to the actual persistent storage. Keeps propogate changes one level. On the other hand, fetching into the child context will output data to each level (through the parent and into the child).

  1. You need to use some form of objectWithID for the managed object. This is the safest (and truly the only) way to transfer objects between contexts. As Tom Harrington noted in the comments, you can use existingObjectWithID:error: though, since objectWithID: always returns an object, even if you pass an invalid ID (which can lead to exceptions). Read more: Link
+46
Jan 11 '13 at 20:19
source share

I had similar problems, and here are the answers to some parts of your questions - 1. You should be able to use the concurrency type NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType 2. Let's say you created a temporary tempContext context with the parent context mainContext (assuming iOS5). In this case, you can simply move the managed entity from tempContext to mainContext to -

 object = (Object *)[mainContext objectWithID:object.objectID]; 

Then you can save mainContext itself.

Perhaps also

 [childContext reset]; 

if you want to reset the temporary context.

+6
Jan 11 '13 at 19:46
source share
  • If you use a parent / child template, you usually declare a parent context with NSMainQueueConcurrencyType and child contexts with NSPrivateQueueConcurrencyType . NSConfinementConcurrencyType used for the classic thread pattern.

  • If you want to keep the context, for some reason you need a strong link to it.

  • You simply call the save method in the child context to make changes to the parent context, if you want to save the data, you also call save in the parent context. You do not need to do this inside the block.

  • There are several ways to get a specific object out of context. I can’t tell you which one will work in your case, try:

    - objectRegisteredForID:

    - objectWithID:

    - existingObjectWithID:error:

+5
Jan 11 '13 at 19:40
source share



All Articles