Follow-up on Sharing Data with WatchKit Extensions
Recently I posted about a potential problem when sharing a Core Data SQLite-backed persistent store between a host app and one or more iOS extensions. The short version of the problem is that there doesn’t seem to be a way to prevent the creation of duplicate entries when syncing model objects from a remote API resource.
On Stack Overflow and elsewhere I got a lot of helpful replies with potential solutions or workarounds:
Accept Duplicates, Clean Up Later – Tom Harrington suggests not trying to prevent duplicates at all, but instead use the same approach that one applies to syncing Core Data via iCloud. Apple’s engineers explicitly caution you to anticipate merge conflicts, like duplicate entries, and to design cleanup strategies from the beginning. He’s written about this approach at length here.
Redesign Extension to be Read-Only – Dave Delong suggests a simple and obvious solution: don’t allow your extension to have write privileges for your shared Core Data store. If your extension design can handle this, it might be the cleanest and easiest approach.
File Coordinators – Apple’s WatchKit Programming Guide recommends using
NSFileCoordinator
to synchronize read/write access to shared file resources. However, in a bizarre technical note they also claim that usingNSFileCoordinator
in an extension can lead to unpreventable deadlocks and should not be used. If Apple ever resolves this issue, usingNSFileCoordinator
might be easier than cleaning up duplicates. Then again, it might be unwieldy, depending upon your persistence architecture.Build Custom Delta-Driven Syncing Architecture – If your remote API resource is not capable of running its own code (as is the case with iCloud and Core Data, for example), Milen Dzhumerov (an engineer behind Clear.app) describes an alternative in which the shared remote resource is not the whole object graph but instead a history of changes. This is essentially how iCloud+CoreData is supposed to work, with the exception that a domain-specific implementation is better suited to resolve conflicts in nondestructive ways. This is indeed how Clear.app was designed to work:
A peculiar property of the system is that the actual object graph is never stored on iCloud, only instructions on how to build it. When new data arrives from other devices, the whole history needs to be reconstructed to be able to obtain the latest object graph. […] When a device joins iCloud, it pushes its current object graph as a set of changes (so it would push create changes for each entity). When a device replays creation changes, it can detect duplicate requests for the creation of the same object (by looking at the object UID).
Milen’s entire article is fascinating and worth a read.
A painfully obvious solution occurred to me after writing my previous post. As long as the remote API resource is a server with its own conflict resolution management, why not just treat the extension as yet another client app? In other words, let each extension have its own complete stack, not sharing anything (except perhaps Keychain credentials and simple user default settings). The worst case scenario is that the extension and the host app are out of sync until both have had an opportunity to re-sync with the remote API, which may be a short-lived problem depending upon the design of your extension.