Reputation: 3991
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
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
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];
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