iOS CoreData+MoGenerator: How do I initialize a Managed Object once only when I am using nested contexts?

StackOverflow https://stackoverflow.com/questions/19144517

  •  30-06-2022
  •  | 
  •  

I am using mogenerator to generate code from a model with a TestPerson managed object. TestPerson inherits from the abstract object TLSyncParent. In TLSyncParent I have the code:

- (void) awakeFromInsert
{
    [super awakeFromInsert];
    QNSLOG(@"%@\n%@", self.managedObjectContext, self.description);
    if (self.syncStatus == nil) {
        self.syncStatusValue = SYNCSTATUS_NEW;
        self.tempObjectPID = [self generateUUID];
        QNSLOG(@"After init values\n%@", self.description);
    }
}

I create the TestPerson object in childMOC whose parent is mainMOC, whose parent is rootMOC. awakeFromInsert runs as expected and makes the init changes. When I save childMOC to mainMOC, awakeFromInsert is run again. From the docs I would not expect that, but there is some ambiguity. From the Docs, "You typically use this method to initialize special default property values. This method is invoked only once in the object's lifetime." The real problem is that when awakeFromInsert runs in mainMOC, the init changes made in childMOC are NOT there. awakeFromInsert is apparently run before the save actually takes place.

2013-10-02 11:22:45.510_xctest[21631:303] TestPerson -awakeFromInsert <NSManagedObjectContext: 0xd684780>
<TestPerson: 0xd6863b0> (entity: TestPerson; id: 0xd684ed0 <x-coredata:///TestPerson/t02B71E0D-AE3F-4605-8AC7-638AE072F2302> ; data: {
    dept = nil;
    job = nil;
    objectPID = nil;
    personName = nil;
    syncStatus = 0;
    tempObjectPID = nil;
    updatedAt = nil;
})
2013-10-02 11:22:45.511_xctest[21631:303] TestPerson -awakeFromInsert After init values
<TestPerson: 0xd6863b0> (entity: TestPerson; id: 0xd684ed0 <x-coredata:///TestPerson/t02B71E0D-AE3F-4605-8AC7-638AE072F2302> ; data: {
    dept = nil;
    job = nil;
    objectPID = nil;
    personName = nil;
    syncStatus = 4;
    tempObjectPID = "7AB46623-C597-4167-B189-E3AAD24954DE";
    updatedAt = nil;
})
2013-10-02 11:22:45.511_xctest[21631:303] CoreDataController -saveChildContext: Saving Child MOC
2013-10-02 11:22:45.511_xctest[21631:303] TestPerson -awakeFromInsert <NSManagedObjectContext: 0xd682180>
<TestPerson: 0xd68fce0> (entity: TestPerson; id: 0xd684ed0 <x-coredata:///TestPerson/t02B71E0D-AE3F-4605-8AC7-638AE072F2302> ; data: {
    dept = nil;
    job = nil;
    objectPID = nil;
    personName = nil;
    syncStatus = 0;
    tempObjectPID = nil;
    updatedAt = nil;
})
2013-10-02 11:22:45.511_xctest[21631:303] TestPerson -awakeFromInsert After init values
<TestPerson: 0xd68fce0> (entity: TestPerson; id: 0xd684ed0 <x-coredata:///TestPerson/t02B71E0D-AE3F-4605-8AC7-638AE072F2302> ; data: {
    dept = nil;
    job = nil;
    objectPID = nil;
    personName = nil;
    syncStatus = 4;
    tempObjectPID = "B799AFDA-3514-445F-BB6F-E4FE836C4F9D";
    updatedAt = nil;
})

What is the proper place to initialize a managed object when using the MoGenerator structure?

有帮助吗?

解决方案 3

OK, thanks to Tom Herrington, I found a very nice way to do this. It seems to do exactly what I want with a minimum of trouble. It fits perfectly with the MoGenerator structure. I already had a category on NSManagedObject with the method initWithMOC. I added a call to the method awakeFromCreate and provided a default implementation. You just override awakeFromCreate in the same way you would override awakeFromInsert. The only requirement is that you ALWAYS create the MO using the initWithMOC method.

@implementation NSManagedObject (CoreDataController)

+ (NSManagedObject*) initWithMOC: (NSManagedObjectContext*) context
{
    NSManagedObject* mo = (NSManagedObject*)
            [NSEntityDescription insertNewObjectForEntityForName: NSStringFromClass(self)
                                          inManagedObjectContext: context];

    [mo awakeFromCreate];
    return mo;
}

- (void) awakeFromCreate
{
    return;
}

其他提示

The documentation on awakeFromInsert is somewhat outdated and doesn't reflect the reality of nested contexts. When it says that the method is

Invoked automatically by the Core Data framework when the receiver is first inserted into a managed object context.

It should really say something like "..first inserted into any managed object context", since (as you've discovered) this happens more than once with nested contexts. Really, the notion of awakeFromInsert is kind of outdated when using nested contexts. The method was clearly designed in the old non-nested days and hasn't adapted.

There are a couple of ways to deal with this. One is a simple run time check, where you do something like:

if ([[self managedObjectContext] parentContext] != nil) {
    // Set default values here
}

This code only runs when the current context is a child of some other context. The method still runs for the parent context, but you skip your default value setters. That's fine if you only ever nest one level deep, i.e. one parent with one or more child contexts, but no "grandchild" contexts of the parent. If you ever add another nesting level, you're right back where you started from.

The other option (and the one I usually prefer) is to move the default value code into a separate method, and then not use awakeFromInsert at all. That is, create a method called something like setDefaultValues, which in your case sets the values for syncStatusValue and tempObjectPID. Call this method right after you first create a new instance and nowhere else. Since it never gets an automatic call, the code never runs except when you tell it to run.

I am pretty sure Mogenerator doesn't change the way you create managed objects, but only moves the actual managed object classes to the machine generated files with the "_" prefix and creates subclasses of those managed objects to put all your custom logic in so that it doesn't get lost when you regenerated your managed object classes.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top