objcio / core-data

Sample code for the objc.io Core Data book
https://www.objc.io/books/core-data
MIT License
770 stars 198 forks source link

Receiving Subscription Update Events Before Uploader Has a Chance to Finish Uploading #12

Open timarnold opened 8 years ago

timarnold commented 8 years ago

Howdy!

I've really been enjoying the book and the sync codebase, and am building off it for our own project.

I'm building a remote type that uses Firebase instead of CloudKit. One thing I encountered is the following:

1) Object A created locally, recognized by Uploader Change Processor. Object A has a nil remoteIdentifier until it is successfully uploaded 2) Upload begins 3) Upload succeeds, but the completion block has not been fired and our app has not yet been notified of a successful upload 4) Object Added Change Subscription from Firebase notifies the app that we have a new object that was added to the remote destination 5) App adds the object from step (4) to the local store 6) Successful upload from step (2) returns to app, and remoteIdentifier is assigned to the local object created in step (1)

Expected result: we should have one object that has been successfully stored in the remote destination

Actual result: we have created two local objects with identical remoteIdentifier values, and there is one object at the remote destination.

I'm not sure this scenario is possible with the Moody sample app, or with CloudKit at all, because of the way remote push updates work with CloudKit. But with the way I added Firebase subscriptions / push updates, it's definitely happening for me.

Is this a scenario you contemplated or experienced at all during your work on the sample app? Do you have any ideas about how this could be avoided?

danieleggert commented 8 years ago

Hi @timcamber — yes, that complicates things. We didn't anticipate this Cloud Kit behaviour.

What does the Object Added Change Subscription look like? Is there anything in there that lets you know that the addition originates from that very same device?

timarnold commented 8 years ago

I'm not a Firebase expert, but I don't think there's inherently information about the origin of the objects coming from the remote destination. Of course we could add this --- I had not thought of adding something like a UDID to model objects. That might be the simplest way to solve this. The only other thing I had thought of was to have a separate object identifier (in fact, our app already has this, so it was a simple quick fix), and not to merge in objects from the subscription if the object already exists locally. I think using a UDID is a better fix.

My subscription with Firebase is set up using their observeEventType(_: FEventType, withBlock: (FDataSnapshot) -> Void ) function, which simply sends an FDataSnapshot object to the closure whenever an update occurs. FDataSnapshot, as far as I can tell, doesn't tell me anything about how it came to be or where it came from.

Edit: in case it's helpful, here is where I set up my subscription (Tasks instead of Moods, and I have tweaked the Remote protocol to accommodate this):

func setupTaskSubscription(updateHandler: ([RemoteRecordChange<RemoteTask>]) -> Void) {
    tasksReference.observeEventType(.ChildAdded) { (snapshot: FDataSnapshot!) -> Void in
        if let contents = snapshot.value as? [String:AnyObject],
            let updatedTask = RemoteTask(record: FirebaseRecord(contents: contents, recordID: snapshot.key)) {
                updateHandler([.Insert(updatedTask)])
        } else {
            updateHandler([])
        }
    }
}

which is invoked in, e.g., my TaskDownloader, like so:

func setupForContext(context: ChangeProcessorContextType) {
    context.remote.setupTaskSubscription { (changes: [RemoteRecordChange<RemoteTask>]) -> Void in
        self.processChangedRemoteObjects(changes, context: context, completion: { _ in })
    }
}

Other than these changes, much of my implementation is the same as in your example.