3

I've a project using Core Data with a default AppDelegate. I've the following thread in my code, where the image for my NSManagedObject WSObject is downloaded. As you will notice, I'm creating a new NSManagedObjectContext for this background thread. I've tried to read the different documentations and other forum topics on the web, but cannot understand how I can notify my main context in the AppDelegate after my object is saved in the background context.

- (void) downloadImageForObjectID:(NSManagedObjectID*)objectID {
    dispatch_queue_t imageDownloaderQueue = dispatch_queue_create("imagedownloader", NULL);
    dispatch_async(imageDownloaderQueue, ^{
        NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
        context.persistentStoreCoordinator = [(AppDelegate *)[[UIApplication sharedApplication] delegate] persistentStoreCoordinator];
        context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;

        WSObject *item = (WSObject*)[context objectWithID:objectID];
        item.image.data = [item.image download];

        if ([context hasChanges]) {
            NSError *error = nil;
            [context save:&error];
        }
    });
    dispatch_release(imageDownloaderQueue);
}

Could someone please tell me what to add to this method and the AppDelegate to get this working? As far as I understand a NSManagedObjectContextDidSaveNotification is sent when I save the context in my background thread. What code should I add to my AppDelegate to listen to this notification and what to do when the notification is received?

EDIT1: I've added the observer to the background thread.

if ([context hasChanges]) {
    NSError *error = nil;


    NSManagedObjectContext *mainContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeHandler:) name:NSManagedObjectContextDidSaveNotification object:mainContext];

    [context save:&error];

    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:mainContext];
}

But the mergeHandler in the AppDelegate is never called.

dhrm
  • 14,335
  • 34
  • 117
  • 183

2 Answers2

12

In your notification handler defined with your AppDelegate class that you register for NSManagedObjectContextDidSaveNotification, you just need to do the following:

- (void)myManagedObjectContextDidSaveNotificationHander:(NSNotification *)notification
{
    // since we are in a background thread, we should merge our changes on the main
    // thread to get updates in `NSFetchedResultsController`, etc.
    [self.managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:NO];
}

Assuming self.managedObjectContext refers to your main NSManagedObjectContext, then that is it.

Easiest is probably to register for your context just before saving and unregister just after:

    if ([context hasChanges]) {
        NSError *error = nil;

       [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myManagedObjectContextDidSaveNotificationHander:) name:NSManagedObjectContextDidSaveNotification object:context];

       [context save:&error];

       [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:context];
    }
gschandler
  • 3,208
  • 16
  • 16
  • Where in the AppDelegate should I add the observer for NSManagedObjectContextDidSaveNotification? And where should I remove the observer? – dhrm Dec 14 '11 at 21:21
  • Added more info that might help – gschandler Dec 14 '11 at 21:23
  • I assume your edit is for the background thread? My background thread does not know the main context, so I cannot do this. Instead of letting each background thread invoke the main context to update, I think it would be useful if the main context listen to the notification itself. – dhrm Dec 14 '11 at 21:24
  • You can certainly do that. But you need the context that you want to observe, or you are going to get ALL `NSManagedObjectContextDidSaveNotification' notifications if you pass `nil` for object when adding an observer. This is why I suggest registering/unregistering just before and just after. The class that this method is in could also maintain a reference to the main context and handle the synching internally (but still create a new context for the background work, of course). – gschandler Dec 14 '11 at 21:38
  • I did. You are passing the wrong `NSManagedObjectContext` to `object:`. Pass the one referenced by `context`, since that is the one you want to monitor. – gschandler Dec 14 '11 at 21:51
  • I can see, that I got my mergeHandler connected properly. But my NSFetchedResultsController using the main context is not updated. I can see in the notification.userInfo that the updates are correct. My UITableViewController uses the protocol `NSFetchedResultsControllerDelegate`. Should the table data not refresh isself automatically when the context is updated? – dhrm Dec 14 '11 at 22:03
  • Yes, it should cause the `NSFetchedResultsController` to call its delegate methods for updating the table based on the changes being merged. – gschandler Dec 14 '11 at 22:24
  • Do you have any clue why his is not happening? I know it is hard for you to guess. – dhrm Dec 14 '11 at 22:35
  • If you are still having issues with your `NSFetchedResultsController` not updating, see my edits. I added code to make the merge happen on the main thread. I think I had the same issue as you are describing, and it is how I recall resolving it. – gschandler Dec 14 '11 at 22:35
  • Thanks for your try -- but sadly it does not solve that issue. – dhrm Dec 14 '11 at 22:47
  • waitUntilDone NO ? – Parag Bafna Oct 19 '17 at 06:41
4

To follow up on gschandler,

in your appDelegate you could to this :

 [[NSNotificationCenter defaultCenter] addObserver:self 
                 selector:@selector(myManagedObjectContextDidSaveNotificationHander:) 
                     name:NSManagedObjectContextDidSaveNotification 
                   object:nil];

if you pass nil as the object you will receive all notification for the name that you have specified, regardless or which object have send it.

NSNotificationCenter Class Reference

notificationSender
The object whose notifications the observer wants to receive; that is, only notifications sent by this sender are delivered to the observer.
If you pass nil, the notification center doesn’t use a notification’s sender to decide whether to deliver it to the observer.


With this you should also receive notification from you main thread context, so you will need to do some filtering to avoid going in circle with main thread saving, getting notify a change occurs and saving again and getting notify etc.

Vincent Bernier
  • 8,674
  • 4
  • 35
  • 40
  • And that might be perfectly ok to do the shotgun approach. I guess you would just need to check the `[notification object]` and make sure it is not the same object as the main context itself. Even then, it may not be a big deal to merge changes from a save to the same context. – gschandler Dec 14 '11 at 21:46
  • @gschandler every time I finish editing, you've added a comment that is close to what I just added, I would give you an other +1 if possible :) – Vincent Bernier Dec 14 '11 at 21:50
  • If I place the addObserver in the AppDelegate on the main context, where in the AppDelegate is the proper place to add the addObserver and the removeObserver? Should I add it to the init and dealloc? – dhrm Dec 14 '11 at 22:06
  • @DennisMadsen basically, add it before you need it, and remove it once you don't need it anymore. So applicationDidFinish... could be a good place to create it, you should also handle while entering the back ground and other life cycle method. But also depending on the way you have architect your app, other place could make even more sense. – Vincent Bernier Dec 14 '11 at 22:31