Search code examples
ioscore-datansmanagedobjectnsmanagedobjectcontextmagicalrecord

How do you access NSManagedObjects between blocks?


Like the title says how does one go about accessing an NSManagedObject that has been created in one block and then needs to be accessed in the other. I have the following implementation and was wondering if it's correct.

__block Person *newPerson;

@weakify(self);
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {

    newPerson = [Person MR_createInContext:localContext];
    newPerson.name = @"Bob";

} completion:^(BOOL success, NSError *error) {
    @strongify(self);
    // Called on main thread

    PersonViewController *personVC = [[PersonViewController alloc] initWithPerson:newPerson];
    [self.navigationController pushViewController:personVC animated:YES];

}];

Am I correct in not needing to access newPerson from a localContext in the completion handler because it'll be executed on the main thread?

EDIT

It looks like the following is the proposed way:

__block NSManagedObjectID *newPersonObjectID;

@weakify(self);
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {

    Person *newPerson = [Person MR_createInContext:localContext];
    newPerson.name = @"Bob";
    newPersonObjectID = newPerson.objectID;

} completion:^(BOOL success, NSError *error) {
    @strongify(self);
    // Called on main thread

    Person *savedPerson = [[NSManagedObjectContext MR_defaultContext] objectWithID:newPersonObjectID];

    PersonViewController *personVC = [[PersonViewController alloc] initWithPerson:savedPerson];
    [self.navigationController pushViewController:personVC animated:YES];

}];

Solution

This answer and comments lead to the following solution. A TemporaryID is being assigned to the object whilst it's being saved and therefore when trying to fetch the object with the TempID an exception occurs.

Rather than creating a whole new fetch request what can be done is asking the context to obtain the permanent IDs early and than acquiring the permanent ID of the object. For example:

__block NSManagedObjectID *newPersonObjectID;

@weakify(self);
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {

    Person *newPerson = [Person MR_createInContext:localContext];
    newPerson.name = @"Bob";

    [localContext obtainPermanentIDsForObjects:@[newPerson] error:NULL];

    newPersonObjectID = newPerson.objectID;

} completion:^(BOOL success, NSError *error) {
    @strongify(self);
    // Called on main thread

    Person *savedPerson = [[NSManagedObjectContext MR_defaultContext] objectWithID:newPersonObjectID];

    PersonViewController *personVC = [[PersonViewController alloc] initWithPerson:savedPerson];
    [self.navigationController pushViewController:personVC animated:YES];

}];

Solution

  • You can't directly pass managed objects between contexts. Each NSManagedObject can only be accessed by its own context.

    You'll need to pass its objectID to the completion block, then have the main context fetch the object by calling one of the following methods:

    -(NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID
    

    This will create a fault to an object with the specified objectID, whether or not it actually exists in the store. If it doesn't exist, anything that fires the fault will fail with an exception.

    -(NSManagedObject *)existingObjectWithID:(NSManagedObjectID *)objectID
                                       error:(NSError **)error
    

    This will fetch the object from the store that has that ID, or return nil if it doesn't exist. Unlike objectWithID, the object won't be faulted; all its attributes will have been retrieved.

    In either case, your local context must have saved the Person object to the store for the main context to be able to fetch it.

    More details about objectID can be found in the Core Data Programming Guide

    Edit by User Asking Question

    This answer and comments lead to the correct solution. A TemporaryID is being assigned to the object whilst it's being saved and therefore when trying to fetch the object with the TempID an exception occurs.

    Rather than creating a whole new fetch request what can be done is asking the context to obtain the permanent IDs early and than acquiring the permanent ID of the object. For example:

    __block NSManagedObjectID *newPersonObjectID;
    
    @weakify(self);
    [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
    
        Person *newPerson = [Person MR_createInContext:localContext];
        newPerson.name = @"Bob";
    
        [localContext obtainPermanentIDsForObjects:@[newPerson] error:NULL];
    
        newPersonObjectID = newPerson.objectID;
    
    } completion:^(BOOL success, NSError *error) {
        @strongify(self);
        // Called on main thread
    
        Person *savedPerson = [[NSManagedObjectContext MR_defaultContext] objectWithID:newPersonObjectID];
    
        PersonViewController *personVC = [[PersonViewController alloc] initWithPerson:savedPerson];
        [self.navigationController pushViewController:personVC animated:YES];
    
    }];