Yaman
Yaman

Reputation: 3991

ios - Core data - app crash if deleting before save finished

My app simply add some users informations (name, birthdate, thumbnail, ...) with Core Data.

I noticed that if I delete a user right after created it, my app just stop working (not a crash, xCode returns no crash log, nothing).

I'm using asynchronous nested context for saving my users informations so I guess that behavior is due to the fact that my delete statement is executing before my save statement.

But since i'm a total beginner with Core Data, i don't really know how to handle that. I don't even know if i declared nested contexts the right way.

Here's my save codes :

NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    tmpContext.parentContext = self.backgroundManagedObjectContext;

    BSStudent *newStudent = (BSStudent *)[NSEntityDescription insertNewObjectForEntityForName:kBSStudent inManagedObjectContext:tmpContext];

    newStudent.firstname = firstname;
    newStudent.lastname = lastname;
    newStudent.birthdate = birthdate;
    newStudent.thumbnail = thumbnail;
    newStudent.createdAt = [NSDate date];

    [self dismissViewControllerAnimated:YES completion:nil];

    [tmpContext performBlock:^{
        [tmpContext save:nil];

        [self.backgroundManagedObjectContext performBlock:^{
            NSError *error;
            if (![self.backgroundManagedObjectContext save:&error]) {
                NSLog(@"%@", [error localizedDescription]);
            }

            [self.managedObjectContext performBlock:^{
                NSError *error;
                if (![self.managedObjectContext save:&error]) {
                    NSLog(@"%@", [error localizedDescription]);
                }
            }];
        }];
    }];

For precision, self.managedObjectContext is a NSPrivateQueueConcurrencyType and self.backgroundManagedObjectContext is a NSMainQueueConcurrencyType. And self.backgroundManagedObject is a child of self.managedObjectContext.

Here's my delete codes :

    BSStudent *student = objc_getAssociatedObject(alertView, kDeleteStudentAlertAssociatedKey);

    // on supprimer l'objet et on sauvegarde le contexte
    [self.managedObjectContext deleteObject:student];
    NSError *error;
    if(![self.managedObjectContext save:&error]) {
        NSLog(@"%@", [error localizedDescription]);
    }

Can someone know how to handle this situation properly ?

Upvotes: 0

Views: 1394

Answers (2)

Tony Million
Tony Million

Reputation: 4296

if you wrap your delete in a performBlock call it can't execute at the same time as the saving performBlock.

e.g.:

BSStudent *student = objc_getAssociatedObject(alertView, kDeleteStudentAlertAssociatedKey);

// on supprimer l'objet et on sauvegarde le contexte
[self.managedObjectContext performBlock:^{
    [self.managedObjectContext deleteObject:student];
    NSError *error;
    if(![self.managedObjectContext save:&error]) {
        NSLog(@"%@", [error localizedDescription]);
    }
}];

This is the "preferred" way of dealing with contexts as it serializes access to the context and keeps all those operations on the contexts thread,

I assume you are getting the crash because the objectID is becoming invalid or changing before the save completes, near the top of the call stack you'll see something about "hash64" or such

Upvotes: 1

Fruity Geek
Fruity Geek

Reputation: 7381

Your delete is probably using the BSStudent created by a different context than you are deleting with. The following code will fix that.

NSManagedObjectContext * deleteContext = student.managedObjectContext;
[deleteContext deleteObject:student];

If you really want to use the other context, refetch the student using ObjectID

NSManagedObject * studentToDelete = [self.managedObjectContext objectWithID:student.objectID];
[self.managedObjectContext deleteObject:studentToDelete];

Nested contexts tips

Your contexts are probably okay, but I see a lot of people throwing around performBlock unnecessarily. With nested contexts, the QueueConcurrencyType refers to the thread it will do Core Data operations on, not the thread it was created on. So doing an operation like save on itself inside its performBlock is unnecessary and can lead to deadlocks.

When you save a child context, the parent is automatically synced with the changes. If you want to save upwards to the next higher parent automatically, I would recommend registering the parent for NSManagedObjectContextDidSaveNotification of the child saves. You can make this easier by having your AppDelegate have a factory method for creating the child contexts.

- (NSManagedObjectContext *)createChildContext
{
    NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    tmpContext.parentContext = self.managedObjectContext;
    //Register for NSManagedObjectContextDidSaveNotification
    return tmpContext;
}

Upvotes: 4

Related Questions