Sharing Core Data between App and Extension
For years now I’ve been avoiding Core Data. My first experience with it was back when the iPad first came out and it wasn’t pleasant.
I was able to ignore it for so long because most of the projects I worked on were just client apps for some web service, with no need for local search, indexing or complex relationship graphs. So I was a little worried when I realised one of my projects needed all three.
I since discover my previous problems with Core Data were just part of the learning curve: threading and performance. With GCD Core Data gained support for queues and blocks, making it considerably easy to work with different threads. Performance is something that depends on the specific project but let’s say that knowing how Core Data actually fetches data from the data store helped a lot (RTFM).
For the project at hand I needed the Core Data store, and if possible context, synced locally between two processes: application and WatchKit extension.
The store file should be placed in a shared container and both processes could create a Core Data stack for that store. Then, when process A’s context is saved, process B can refresh it’s stack (or even better, merge the changes in it’s object context). If your data model is simple enough (mine is) you can get away with minimum merge situations.
Now my only problems were the save and refresh steps. Having to count on the user to refresh for new data to appear is not a great experience. Also, making objects appear seamsly on the Watch as the user adds them on the phone… That’s the effect I want, Magic!
The objects can be saved automatically. As the user adds an object the context gets saved, but communicating that to the other processes was out of the scope of Core Data. Luckily my attention was called to a blog post by Damien DeVille where he sums up the state of Interprocess communication on iOS and suggests the use of Berkeley Sockets.
You see, on Unix you can create a socket for a file. And multiple clients can “connect” to that file and communicate. Since the shared container is shared between application and extension that makes it the place to put that file.
That method is very interesting and can be used to, not only notify both processes that data has changed, but also to communicate what changed. And apparently Apple prefers it to Mach ports (which also work on iOS).
In the end I decided to go with something simpler, the notify routines. Since those are stateless notifications, distributed by a system-wide notification server, you can notify as many clients as you want. Also, it’s dead simple to configure:
//Register for notifications
var registrationToken : Int32 = 0
notify_register_dispatch(
"org.fbernardo.app.coredata.refresh",
®istrationToken,
dispatch_get_main_queue()) {
[unowned self] token in
self.refresh()
}
//Post notification
notify_post("org.fbernardo.app.coredata.refresh")
Just remember that if your app registers for a given notification event it will be called even if the origin was the same app. Also, on the example above the main queue wouldn’t block because notify_post is asynchrounous.