2
votes

Let's say I have a NSManagedObject named «Picture» that I have created via RestKit. I'm uploading the actual picture file content through a NSOperation. That NSOperation is tracking the upload progress, and saving it in the «progress» attribute of the Picture object.

if(![self isCancelled]) {
    Picture *pic = [Picture findFirstByAttribute:@"pictureId" withValue:self.pictureId];
    if(![pic isDeleted]) {
        pic.progress = [NSNumber numberWithFloat:progress];
        [[RKObjectManager sharedManager].objectStore save:NULL];
    }
}

My view controller is displaying a list of Picture objects, with the help of a NSFetchedResultsController. Upload works great, RestKit is doing the thread safety and the merge back to the main NSManagedObjectContext, so the UI is displaying the upload progress as expected.

Imagine now that while uploading the picture the user presses the «cancel» cross, which is also supposed to delete the Picture object. In the controller, I'm calling a cancel on the NSOperation, which effectively stops the running the operation within milliseconds, and the object is deleted from CoreData on the main thread.

[uploadOperation cancel];
Picture *pic = [Picture findFirstByAttribute:@"pictureId" withValue:self.pictureId];
[pic deleteEntity];
[[RKObjectManager sharedManager].objectStore save:NULL];

Traces show that I get a NSFetchedResultsChangeDelete on my NSFetchedResultsControllerDelegate as expected. However it is immediately followed by a NSFetchedResultsChangeInsert and a NSFetchedResultsChangeUpdate, because the saving of the progress is made really frequently by the NSOperation. It seems that the NSManagedObjectContext used by the NSOperation is out of sync. It doesn't know that the Picture object is deleted, the call to save: causes an insert and an update.

So in my UI (and in DB), my Picture object remains while it's supposed to be deleted.

Looking at RKManagedObjectStore, it seems that merges are made from background threads (NSOperation) to main thread (my view controller) but not the opposite way.

What's the best approach to fix this issue ?

2

2 Answers

0
votes

I think you should probably change the control flow of the app a bit. Deleting the object in the main context while the NSOperation is still in progress sounds like a bad idea. Why don't you e.g. check if the operation was cancelled in the NSOperation itself and delete the image objects from there, using its MOC? In that way you only have writes on one child MOC. The alternative, i.e. updating the child MOC in the operation before every modification defeats the purpose of having separate contexts.

Thinking about it, there is probably some clever way of observing NSManagedObjectContextObjectsDidChangeNotification and merging with an appropriate NSMergePolicy, but then again, I think that's overkill for your case. I'd just handle the entire cancellation and deletion in the same place.

0
votes

Sounds like the perfect situation to use the Core Data Undo Manager. You'll find a good example over here or in the CoreDataBooks Example provided by Apple.