3
votes

I've got an app design question that I'm hoping someone can help with.

Let's take a very simple setup: Core Data app for displaying news items from a server.

  • Main thread / UI has a managed object context that's used by all the view controllers to display the data.

  • An NSOperation runs in the background checking the server, with it's own context, on the same persistent store.

I want to merge the changes in the background context so I use NSManagedObjectContextObjectsDidChangeNotification.

According to the Apple docs:

Several system frameworks use Core Data internally. If you register to receive these notifications from all contexts (by passing nil as the object parameter to an addObserver… method), then you may receive unexpected notifications that are difficult to handle.

So, I want to filter my notifications merged in the main thread MOC to just those changes coming from the background operation MOC.

What's the cleanest way to get/maintain a reference to the background operation MOC so that I have something to plug into the addObserver method and the notifications are properly filtered? I can think of a lot of ways that involve a lot of coupling but they all seem like a hack.

Any suggestions or ideas? How are others handling this?

4
So, the primary thrust of my question is how to handle this with the minimum amount of coupling between components. For instance, front-end stuff like view controllers shouldn't have to know anything about back-end stuff like NSOperations. - Hunter

4 Answers

4
votes

Here's how it works in my app:

// should be executed on a background thread
- (void)saveWorkerContext {
    if ([_workerContext hasChanges]) {
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];    
        [nc addObserver:self selector:@selector(workerContextDidSave:)
                   name:NSManagedObjectContextDidSaveNotification object:_workerContext];

        NSError *error;
        if (![_workerContext save:&error]) {
            NSAssert(NO, @"Error on save: %@", error);
        }

        [nc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:_workerContext];
    }
}

- (void)workerContextDidSave:(NSNotification *)notification {
    if (_mainContext) {
        [_mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) 
                                       withObject:notification waitUntilDone:NO];
    }
}
2
votes

--- Revised Answer ---

Using NSFectchedResultsController appears to be your best bet. It will inform it's delegate when changes to it's MOC effect it's results. This negates the need for your view controller to know about or directly observe events from your background MOC.

Here's the pattern I'd use with NSOperation worker processes.

Store the background MOC in a NSOperationQueue subclass with a maxConcurrentOperationCount of 1. This ensures the operations will occur serially.

  • Subclass NSOperationQueue
  • Add a property for the background MOC
  • Implement the NSOperationQueue's MOC getter to lazily create the background MOC from the Persistent store and register the class responsible for the merging the context with the background MOC did save notification (Typically your AppDelegate or singleton)
  • Unregister observations for the class and clean up the background MOC in dealoc

Create your NSOperationQueue subclass.

Before adding operations, provision them with the background MOC from the queue's background MOC property. When scheduled, your operation will perform work with the background MOC and save.

Perform the merge when the did save notification comes in on the observing class. After the merge, each fetched results controller using the foreground MOC will notify it's delegate when any changes had an impact on it results. This includes additions or deletions from the background MOC merge.

1
votes

I am not sure I fully understand your question: if you have just one background thread associated to one particular MOC you want to track, then there is nothing special to do:use a property to maintain a reference to the MOC. You handle this as usual, as shown in the following code snippet.

// create a new MOC
self.backgroundMOC = ...; 

// register to receive notifications
[[NSNotificationCenter defaultCenter] addObserver:self
                                      selselector:@selector(contextDidSave:) 
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:self.backgroundMOC];


// pass backgroundMOC to your background thread
// and handle notifications here
- (void)contextDidSave:(NSNotification *)notification
{
        NSManagedObjectContext *MOC = (NSManagedObjectContext *) [notification object];
        if([MOC isEqual:self.backgroundMOC])
           [managedObjectContext mergeChangesFromContextDidSaveNotification:notification];

}
1
votes

If you don't want to have any communication between the threads/operations that hold the context, then the only way to identify if a context generating a notification belongs to you would be to check its persistent store url.

Only your context would have your store URLs. The API URLs will have system stores or in-memory stores.

Normally, of course, you do communicate between processes and can just pass the object references for identification purposes.