NSManagedObjectContextDidSaveNotification and Simperium

1.4k Views Asked by At

I am not getting NSManagedObjectContextDidSaveNotification notifications from the NSManagedObjectContext I give to Simperium.

Basically what I like to do is inform my application when an update of the database has happened due to an update by simperium.

Therefore I am using 2 NSManagedObjectContexts, one for my application and the other one for Simperium. Once my application saves its context, the changes are merged to the simperium context in the NSManagedObjectContextDidSaveNotification notification via mergeChangesFromContextDidSaveNotification:.

The problem however is that whenever there is an update to the Simperium NSManagedObjectContext, I don't get a NSManagedObjectContextDidSaveNotification and thus I can't merge the changes from remote to my application context. I checked that it actually save the data to the persistent store.

Some code:

- (void) setSyncingEnabled:(BOOL)syncingEnabled
{
    if (syncingEnabled && !self.simperium)
    {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(syncingObjectContextDidSaveNotification:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:self.syncingObjectContext];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(objectContextDidSaveNotification:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:self.objectContext];

        self.simperium = [[Simperium alloc] initWithRootViewController:App.delegate.window.rootViewController];
        self.simperium.authenticationOptional = YES;
        [self.simperium addDelegate:self];

        [self.simperium startWithAppID:kSimperiumAppId
                                APIKey:kSimperiumAPIKey
                                 model:self.objectModel
                               context:self.syncingObjectContext
                           coordinator:self.storeCoordinator];
    }
    else if (self.simperium) {
        self.simperium = nil;

        [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.syncingObjectContext];
        [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.objectContext];
    }
}


- (void) objectContextDidSaveNotification:(NSNotification*)notification
{
    if (self.simperium) {
        [self.syncingObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }
}

- (void) syncingObjectContextDidSaveNotification:(NSNotification*)notification
{
    [self.objectContext mergeChangesFromContextDidSaveNotification:notification];

    /* Inform Application */
    [[NSNotificationCenter defaultCenter] postNotificationName:DatabaseDidUpdateExternallyNotification object:self];
}
2

There are 2 best solutions below

1
On BEST ANSWER

Since your goal is to inform your app when an update has happened via Simperium, there are better options than using a separate context (which Simperium does internally anyway):

1) You can use an NSFetchedResultsController which will get called as objects are inserted, changed, and removed.

2) You can use SimperiumDelegate to react to specific changes. This works a bit differently in the release that is currently being tested in the "iosupdate" branch on GitHub. The SimperiumDelegate protocol will be replaced by SPBucketDelegate to give you more control over the notifications you care about.

If for some reason you really need to use a second context, please get in touch so we can talk about your use case a little more.

0
On

I dug a little deeper and found out that the Simperium SDK is not using the object context you give it, but another object context that's created privately. That's why the context you give it gets never saved. There is currently no public API to get to the private concurrent context.

If you don't want to use NSFetchedResultsController, because you sync a lot of objects and the performance isn't that great, you can also simply send out one notification after objects have been updated to handle updating your UI.

The following code seems to work (master branch):

-(void)objectKeysChanged:(NSSet *)keyArray entityName:(NSString *)entityName
{
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(informAppAboutExternalChange) object:nil];
    [self performSelector:@selector(informAppAboutExternalChange) withObject:nil afterDelay:0.5];
}

- (void) informAppAboutExternalChange
{
    [[NSNotificationCenter defaultCenter] postNotificationName:kObjectsDidChangeExternallyNotification object:nil];
}

Simply observe the notification wherever you need to update your UI. The delayed perform should also prevent the notification to be triggered too often, which would also slow down the performance.