One shot replication in objective-c

I’m trying to implement a one-shot pull replication method that does a call back when it’s complete. In java my implementation is to add a change listener, and use the status of the event to detect when its stopped:

sync.addChangeListener(new Replication.ChangeListener() {
    @Override
    public void changed(Replication.ChangeEvent event) {
        if (event.getSource().getStatus() == Replication.ReplicationStatus.REPLICATION_STOPPED) {
            Log.i(TAG, "one-shot replication status is: " + event.getSource().getStatus());
            oneShotSyncListener.syncComplete();
        }
    }
});

What’s a suitable way of doing this in objective-c? I see that I can listen to the events in a similar way:

[[NSNotificationCenter defaultCenter] addObserver: myObserver
                                         selector: @selector(replicationChanged:)
                                             name: kCBLReplicationChangeNotification
                                           object: repl];

But I’m not quite sure what to do in the replicationChanged method. Is there data in the NSNotification that has the information I need? Or do I need to query the underlying CBLReplication object?

Look at the replication object’s status property.

Thanks for the reply Jens.

The problem I have is that my observer only gets called once, with a status of Offline. I have a quick look at the code, and I think this is just an initial state, and my app isn’t actually offline (other replication tasks are working fine). After this it never gets called again, so I can’t test the status. Now I think the app happens to be in a state where there is no work for the replicator to do. Is this possibly why the observer only gets called once?

No, if there’s no work for the replicator it should go to either Idle or Stopped status, depending on whether it’s continuous.

Only one replicator has this problem? You have others that are working correctly? If so, how is this one different from the others?

Yes. So I’ve created a push and pull replication using the REST API which are working fine. I would like to un-suspend them on receipt of a push notification temporarily, but as discussed elsewhere that’s not possible right now. So, I tried creating a ‘one-shot’ replication in objective-c which i instantiate every time I receive a notification… which I think is working - still testing, it doesn’t seem to work 100% of the time, but thats another issue.

I then decided to reuse the idea to get my app syncd up when it starts. Getting a call-back for the completion of pull replication seemed like a better solution than polling the status using the rest api. However, when I first tested I only ever got the 1 notification, and when inspecting the status of the replication object it reported to be ‘offline’.

I looked in the code, line 307 of CBLReplication.m seemed to be a likely cause:

// Initialize the status to something other than kCBLReplicationStopped:
        [self updateStatus: kCBLReplicationOffline error: nil processed: 0 ofTotal: 0
             lastSeqPushed: 0 serverCert: NULL];

What thread is your Obj-C code running on? The thread has to have a runloop, otherwise it won’t receive events, such as notifications. (You almost certainly want to do this on the main thread.)

Ah, I’ve no idea but will look in to it. Thanks for the tip.

Do I need to clean up one-shot replications when they’re finished, or do they naturally get removed from the system.

One-shot replications get cleaned up after they stop.

Thanks Jens, that’s helpful.

How soon are the one shot replications removed? They seem to be accumulating in my app. I create 2 each time I receive a push notification, and they don’t seem to be going away. Each time I receive a notification I’m accumulating 2 more, I’m a bit worried they’ll blow my app up eventually.

I’m logging them each time a notification arrives and they’re all have running=true and suspended=false. The have 20 seconds before the app goes back to sleep which should be plenty of time as each notification usually represents just one document change. Here’s the code:

//gets call by the app on receipt of a push notification
RCT_EXPORT_METHOD(oneShotSync:(NSString*)syncURL dbName:(NSString*)dbName userId:(NSString*)userId password:(NSString*)password)
{
    NSURL* url = [NSURL URLWithString: syncURL];
    
    [self pullSync:url dbName:dbName userId:userId password:password syncObserver:nil];
    [self pushSync:url dbName:dbName userId:userId password:password syncObserver:nil];
}


- (void) pullSync:(NSURL*)syncURL
           dbName:(NSString*)dbName
           userId:(NSString*)userId
         password:(NSString*)password
           syncObserver:(SyncObserver*) syncObserver
{
    runOnMainQueueWithoutDeadlocking(^{
        NSLog(@"Starting one-shot pull sync for %@ to %@", dbName, syncURL.absoluteString);
        
        CBLManager *manager = [CBLManager sharedInstance];
        CBLDatabase* database = [manager databaseNamed:dbName error:nil];
        
        CBLReplication* repl = [database createPullReplication:syncURL];
        syncObserver.repl = repl;
        
        repl.authenticator = [CBLAuthenticator basicAuthenticatorWithName: userId password: password];
        
        if(syncObserver) {
            [[NSNotificationCenter defaultCenter] addObserver: syncObserver
                                                     selector: @selector(replicationChanged:)
                                                         name: kCBLReplicationChangeNotification
                                                       object: repl];
        }
        
        [repl start];
    });
}

- (void) pushSync:(NSURL*)syncURL
           dbName:(NSString*)dbName userId:(NSString*)userId
         password:(NSString*)password
           syncObserver:(SyncObserver*) syncObserver
{
    runOnMainQueueWithoutDeadlocking(^{
        NSLog(@"Starting one-shot push sync for %@ to %@", dbName, syncURL.absoluteString);
        
        CBLManager *manager = [CBLManager sharedInstance];
        CBLDatabase* database = [manager databaseNamed:dbName error:nil];
        
        CBLReplication* repl = [database createPushReplication:syncURL];
        syncObserver.repl = repl;
        
        repl.authenticator = [CBLAuthenticator basicAuthenticatorWithName: userId password: password];
        
        if(syncObserver) {
            [[NSNotificationCenter defaultCenter] addObserver: syncObserver
                                                     selector: @selector(replicationChanged:)
                                                         name: kCBLReplicationChangeNotification
                                                       object: repl];
        }
        
        [repl start];
    });

void runOnMainQueueWithoutDeadlocking(void (^block)(void))
{
    if ([NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}
}

They’re removed when they stop. The ones you’re describing haven’t stopped yet…