2
votes

In my Core Data app I am initializing Managed Objects using initWithEntity:insertIntoManagedObjectContext: with a nil Context. I'm then using [managedObjectContext insertObject:object] to insert the object into the Context, if/when needed.

It seems that this is why the insertObject: API exists. To insert a Managed Object that was not inserted during creation.

But when the above logic is used, the objects (when later fetched from the MOC) have nil properties. The strings on the object are not persisted.

If I instead use initWithEntity:insertIntoManagedObjectContext: with a non-nil MOC, the properties are persisted and everything works correctly. In this case, I do not call insertObject:.

The two variances of the code are below. Simply toggle the useNilContext flag to try each.

// ...

BOOL useNilContext = YES;

[[XYZBackend sharedBackend].managedObjectContextPrivateQueue performBlock:^{
    XYZObject *object = [XYZObject objectFromJSON:json useNilContext:useNilContext];

    if (useNilContext) {
        [[XYZBackend sharedBackend].managedObjectContextPrivateQueue insertObject:object];
    }

    NSError *privateQueueError = nil;
    if (![[XYZBackend sharedBackend].managedObjectContextPrivateQueue save:&privateQueueError]) {
        NSLog(@"Error saving Core Data managed object context in private queue! %@", privateQueueError);
    }
}];

// ...


+ (instancetype)managedObjectFromJSON:(NSDictionary *)json useNilContext:(BOOL)useNilContext {
    __block XYZManagedObject *object = nil;

    [[XYZBackend sharedBackend].managedObjectContextPrivateQueue performBlockAndWait:^{
        NSEntityDescription *entity = [NSEntityDescription entityForName:NSStringFromClass([self class]) inManagedObjectContext:[XYZBackend sharedBackend].managedObjectContextPrivateQueue];

        NSManagedObjectContext *context = [XYZBackend sharedBackend].managedObjectContextPrivateQueue;
        if (useNilContext) {
            context = nil;
        }

        object = [[XYZManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:context];

        object.stringProperty = json[@"string"];
    }];

    return object;
}

Later, when I fetch the object from Core Data and inspect it, I get different behavior depending on if the object was initially inserted into a MOC. If the MOC was nil, all of the object's properties are nil after the fetch.

Using initWithEntity:insertIntoManagedObjectContext: with a nil MOC:

Printing description of object:
<XYZObject: 0x60b0000b80d0> (entity: XYZObject; id: 0xd000000000040000 <x-coredata://5F843602-C03B-4339-AC50-0F70FD1545C9/XYZObject/p1> ; data: {
    stringProperty = nil;
})

Initializing with a non-nil MOC:

Printing description of object:
<XYZObject: 0x60b000054680> (entity: XYZObject; id: 0xd000000000040000 <x-coredata://72502855-7204-4485-828C-BC07C51F7FE2/XYZObject/p1> ; data: {
    stringProperty = "string";
})

As you can see, the properties are nil after the fetch when the object was not initially inserted into a MOC. When it is initially inserted into the MOC, the properties are non-nil and persisted.

Why are these properties not persisted? I would think that the two code strategies should be identical.

Is there another step that must be taken to persist the properties of a Managed Object that initially has a nil Managed Object Context?

1
Have you tried inspecting the object stringProperty right before and right after setting it. Is it actually being set in both cases? - Yan

1 Answers

0
votes

No, there's no extra step. As for why the properties aren't saved, there's something else going on in your code that's causing the problem. Allocating a new managed object works the same whether you provide a context or whether you use a nil context but then insert the object.

It looks like you may be mixing up contexts, but it's hard to be sure. When you call insert:, you use [TDBackend sharedBackend].managedObjectContextPrivateQueue. But when you save changes, you use self.managedObjectContextPrivateQueue. Meanwhile, when useNilContext is NO, you create the managed object using [XYZBackend sharedBackend].managedObjectContextPrivateQueue. And you're synchronizing things with a block call on [ZYZBackend sharedBackend].managedObjectContextPrivateQueue. That's four different contexts in what should be a fairly simple bit of code. It's impossible to be certain but it gives the distinct impression that you're using the wrong context at some point