Search code examples
ioscore-datansmanagedobjectcontext

Illegal attempt to establish a relationship 'checklists' between objects in different contexts


I'm using Core Data for the offline storage of my iOS app, and I keep getting the following error:

Illegal attempt to establish a relationship 'checklists' between objects in different contexts

The problem is, I'm definitely using the same context to create the relationship. Below is the block of code that is triggering the issue:

 NSMutableSet *checklists = [[NSMutableSet alloc] init];
for (NSDictionary *dict in array){
    Checklists *checklist = [self addChecklist:dict withContext:context];
    [checklists addObject:checklist];
    DLog(@"context: %@", checklist.managedObjectContext);
}

//Delete any lists that aren't from the server
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
Users *currentUser = (Users *)[self getUserWithId:[defaults objectForKey:@"user_id"] withContext:context];
DLog(@"context: %@", context);
currentUser.checklists = checklists;

The exception is thrown at the last line, currentUser.checklists = checklists. The log from the context is the same, and the objects are in the same thread. Has anyone run into this before?

Code for addChecklist:withContext::

- (Checklists *) addChecklist:(NSDictionary *) dict withContext:(NSManagedObjectContext *)context{
    //Pull the list from the database and update
    Checklists *checklist = [self getChecklistWithId:dict[@"_id"] withContext:context];

    //If the list doesn't exist, add it to the database
    if(!checklist)
        checklist = (Checklists *)[NSEntityDescription insertNewObjectForEntityForName:@"Checklists" inManagedObjectContext:context];

    //Update or create the list attributes
    checklist.checklist_id = dict[@"_id"] ? dict[@"_id"] : [NSString stringWithFormat:@"%d", (arc4random() % 2000)];
    checklist.desc = dict[@"description"] == [NSNull null] ? nil : dict[@"description"];
    checklist.name = dict[@"name"] == [NSNull null] ? nil : dict[@"name"];
    checklist.published = dict[@"published"] == [NSNull null] ? 0 : dict[@"published"];
    checklist.created_at = dict[@"created_at"] == [NSNull null] ? nil : dict[@"created_at"];
    checklist.updated_at = dict[@"updated_at"] == [NSNull null] ? nil : dict[@"updated_at"];

    [self addChecklistSections:dict[@"checklist_sections"] withChecklist:checklist withContext:context];

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    [self saveMasterContext];

    return checklist;
}

Code for getUserWithId: withContext:

- (Users *) getUserWithId:(NSString *)user_id withContext:(NSManagedObjectContext *) context
{
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Users" inManagedObjectContext:context];
    [request setEntity:entity];
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"(user_id = %@)", user_id];
    [request setPredicate:pred];

    NSError *error = nil;
    NSMutableArray *users = [[context executeFetchRequest:request error:&error] mutableCopy];
    if (users == nil || users.count == 0) {
        return nil;
    }
    return users[0];
}

Solution

  • I do not see the motivation of making a mutableCopy of your fetched users. I think that might be the reason you get the context error. In theory it should just make a copy of the pointers to your objects, but who knows what's happening behind the scenes.

    Instead, set the fetch request's fetchLimit to 1 and just check if you get exactly one results. Return directly this result, not a copy thereof.

    It is also not clear why you are calling a context save method (saveMasterContext) when you anyway pass the context. It would be more logical to call

    [context save:nil];
    

    Also, following Apple's copious sample code you should consider making the context a @property of the class rather than passing it around as method arguments.

    --

    BTW, I really think you should drop the habit of naming your entities in the plural. A checklist is really a checklist, not a checklists.

    Also, an id scheme relying on strings is not very efficient. Consider using a numeric equivalent. Also, I find assigning a random checklist id rather risky.

    Also, you have a few stray lines in the code such as defining an unused variable for user defaults just before returning from addChecklist.