How to automatically setup Core Data relationship when using nested contexts

cocoa, core-data, ios, macos, objective-c

Solution

I ended up going with the convenience method solution. All the NSManagedObject subclasses in my app have a `+insertInManagedObjectContext:` method. Creating instances of those objects (in my own code) is always done using that method. Inside that method, I do this:

+ (instancetype)insertInManagedObjectContext:(NSManagedObjectContext *)moc
{
    MyManagedObject *result = [NSEntityDescription insertNewObjectForEntityForName:@"MyEntityName" inManagedObjectContext:moc]
    [result awakeFromCreation];
    return result;
}

- (void)awakeFromCreation
{
    // Do here what used to be done in -awakeFromInsert.
    // Set up default relationships, etc.
}

As for the NSArrayController issue, solving that isn't bad at all. I simply created a subclass of NSArrayController, overrode `-newObject`, and used that subclass for all the relevant NSArrayControllers in my app:

@implementation ORSManagedObjectsArrayController

- (id)newObject
{
    NSManagedObjectContext *moc = [self managedObjectContext];
    NSEntityDescription *entity = [NSEntityDescription entityForName:[self entityName]
                                              inManagedObjectContext:moc];
    if (!entity) return nil;

    Class class = NSClassFromString([entity managedObjectClassName]);
    return [class insertInManagedObjectContext:moc];
}

@end

Problem

I'm struggling to figure out a decent solution to a problem that arises when using nested Managed Object Contexts in Core Data. Take a model that has two enites, Person and Name, where each Person has a one-to-one relationship with a Name, and Name's person relationship is not optional. Previously, in Person's `-awakeFromInsert` method, I would automatically create a Name entity for the new Person: ``` - (void)awakeFromInsert { [super awakeFromInsert]; NSManagedObjectContext *context = [self managedObjectContext]; self.name = [NSEntityDescription insertNewObjectForEntityForName:@"Name" inManagedObjectContext:context]; } ``` This works just fine in a single, non-nested managed object context. However if the context has a parent context, when the child context is saved, a new Person object is created in the parent context, and `-awakeFromInsert` is called again on this new object before the original Person's properties and relationships are copied over. So, another Name object is created, then "disconnected" when the existing name relationship is copied over. The save fails because validation of the floating Name's now-nil `person` relationship fails. This problem is described here as well as in other places. So far, I've been unable to come up with a good solution to this problem. Lazily creating the relationship in the getter method actually causes the same problem, because the getter is called by internal Core Data machinery when the new Person is created in the parent context. The only thing I can come up with is to forgo automatic relationship generation, and always create the relationship explicitly either in the controller class that creates the Person, or in a convenience method (e.g. `+[Person insertNewPersonInManagedObjectContext:]`) that is only called by my code, and is always the method used to create a new Person object explicitly. Perhaps this is the best solution, but I'd prefer to not have to be so strict about only allowing a single method to be used to create managed objects, when other creation methods that I have no control over and whose use I can't easily check for/exclude, exist. For one thing, it will mean multiple NSArrayController subclasses to customize the way they create managed objects. Has anyone else that has encountered this problem come up with an elegant solution that allows for one NSManagedObject to create a relationship object automatically upon creation/insertion?

Original source