MR_inContext returns nil for just-created (and saved) object #184

Closed
justinrosenthal opened this Issue Jun 17, 2012 · 6 comments

Projects

None yet

7 participants

@justinrosenthal

I have a simple test app, there are two buttons: "Create" and "Modify"

When I hit "Create", I am creating an entity on the main thread (in the default context) and saving it. Inspecting the sqlite file at this point in the execution shows that the object has indeed been persisted.

When I hit "Modify" I find the just-created object (works fine), and then i attempt a saveInBackgroundWithBlock... where the first thing it does is try to get the object we just found, but in the local context via MR_inContext:localContext but it always returns nil.

Seems possible that I'm doing something stupid, but would love to know what it is or if this is actually a bug.

- (void)create {
    assert([NSThread isMainThread]);
    TestModel *model = [TestModel MR_createEntity];
    model.color = [NSNumber numberWithInt:0];
    [[NSManagedObjectContext MR_defaultContext] MR_save];
}

- (void)modify {
    TestModel *model = [TestModel MR_findFirstByAttribute:@"color" withValue:[NSNumber numberWithInt:0]];
    assert(model != nil);

    [MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
        TestModel *localModel = [model MR_inContext:localContext];
        assert(localModel != nil);  // <---- This assertion fails

        localModel.color = [NSNumber numberWithInt:(arc4random() * 0xffffff)];
        [localContext MR_saveNestedContexts];
    }];
}
@michalkrause

I spent last week with this issue. It seems to me that problem is in objectID of newly created object - even after context is saved, objectID is still temporary (you can test it with [model.objectID isTemporaryID]). I didn't found other solution than replace following code

TestModel *localModel = [model MR_inContext:localContext];

with

TestModel *localModel = [localContext objectWithID:model.objectID];

You may need to call MR_saveNestedContexts instead of MR_save in create method too. It works, but I'm pretty new to Core Data and I'm not sure, if it is the right solution. Also, I'd like to know, why this problem happens. It doesn't seem to be MR related - it is probably behaviour of Core Data layer.

@elbryan
elbryan commented Jun 26, 2012

I had the same problem you have. I also ended up using the objectID and fetching the object using the solution you proposed.

Still, in the documentation it is written to not call the save method in the block. Try removing that line.

@onomated

I'm having the same issue even after calling MR_saveNestedContexts. I create a child context for my edit (and add) operations on managed objects as follows:

NSManagedObjectContext *scratchpad = [NSManagedObjectContext MR_contextThatPushesChangesToDefaultContext];
Foo *newObj = [Foo MR_createInContext:scratchpad];

If the user chooses to save, I call MR_saveNestedContexts, and attempt to get the newly saved object back in the default context as follows:

[scratchpad MR_saveNestedContexts];
Foo *obj = [newObj MR_inContext:[NSManagedObjectContext MR_defaultContext]];

This results in nil obj.

Replacing with the following works:

[scratchpad MR_saveNestedContexts];
Foo *obj = (Foo *)[[NSManagedObjectContext MR_defaultContext] objectWithID:[newObj objectID]];
@blackgold9
Member

I'm considering a fix for this class of issues that would simply always get a permanent id before saving, to eliminate the chance of this happening. It's a bit slower, but the confusion of not doing it might be worse

@tonyarnold
Collaborator

I believe #540 fixes this issue. Once it’s merged if the issue reappears, please feel free to re-open this thread.

@tonyarnold tonyarnold closed this Aug 19, 2013
@sryze
sryze commented Oct 25, 2016 edited

I'm experiencing this problem with MagicalRecord 2.3.2.

Objects are created in a child MOC like this (I'm using PromiseKit here):

_managedObjectContext = [NSManagedObjectContext MR_context];

__weak typeof(self) weakSelf = self;
__block NSManagedObject *mappedObject;

return [self performBlockOnManagedContextQueue:^{
    mappedObject = ...; // create a new object inside self.managedObjectContext here
}].then(^{
    [weakSelf.managedObjectContext save];
}).then(^{
    return [mappedObject MR_inContext:[NSManagedObjectContext MR_defaultContext]];
});

The performBlockOnManagedContextQueue and save methods is simply a wrapper around performBlock: that returns a promise:

- (PMKPromise *)performBlockOnManagedContextQueue:(void(^)())block {
    NSAssert(block != nil, @"Block cannot be nil");
    return [PMKPromise promiseWithResolver:^(PMKResolver resolve) {
        [self.managedObjectContext performBlock:^{
            block();
            dispatch_async(dispatch_get_main_queue(), ^{
                resolve(nil);
            });
        }];
    }];
}

@implementation NSManagedObjectContext (PromiseKit)

- (PMKPromise *)save {
    return [PMKPromise promiseWithResolver:^(PMKResolver resolve) {
        [self MR_saveToPersistentStoreWithCompletion:^(BOOL contextDidSave, NSError *error) {
            if (error == nil) {
                resolve(@(contextDidSave));
            } else {
                resolve(error);
            }
        }];
    }];
}

@end

Even though I save the default context prior to calling MR_inContext: it still returns nil for some reason. Could it be that the save operation hasn't finished yet when MR_saveToPersistentStoreWithCompletion called the completion block?

I have also tried replacing the MR_inContext call with this as suggested in earlier posts:

[[NSManagedObjectContext MR_defaultContext] objectWithID:object.objectID];

But it resulted in objects with temporary object IDs and I was getting a crash at a later point:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'cannot find data for a temporary oid: <ID goes here>' 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment