The WatchKit SDK was released yesterday, emphasizing an interesting problem that affects all iOS 8 extensions that need to share a Core Data store with their host app. Consider this scenario:
A host iOS 8 app.
One or more iOS 8 extensions (WatchKit, Share, etc.) bundled with the host app.
The host app and all extensions share the same Core Data SQLite store in the shared app group container.
Each app/extension has its own NSPersistentStoreCoordinator and NSManagedObjectContext.
Each persistent store coordinator uses a persistent store that shares the same SQLite resources in the group container as all the other persistent stores.
The app and all extensions use a common codebase for syncing content from a remote API resource on the Internet.
Sequence of Events Leading to the Problem
The user launches the host app. It begins fetching data from the remote API resource. Core data model objects are created based on the API response and “upserted” into the host app’s managed object context. Each API entity has a uniqueID that identifies it in the remote API backend. By “upsert,” I mean that for each API entity, the host app only creates a new entry in Core Data if an existing entry for a given uniqueID cannot be found.
Meanwhile, the user also launches one of the host app’s extensions. It, too, performs some kind of fetch from the same remote API. It also attempts to perform an “upsert” when parsing the API responses.
The Problem: What happens if both the host app and an extension try to upsert a Core Data entry for the same API entity at the same time? To see how this could come about, let’s look at the sequence of events for an upsert:
Core Data Upsert Sequence
The API parsing code parses the uniqueID for a given API entity.
The parser performs a Core Data fetch for any entry that matches a predicate where
uniqueIDis equal to the parsed uniqueID.
If an existing entry is not found, the parser inserts a new Core Data entry for this API entity, set’s its
uniqueIDattribute to the parsed uniqueID.
The parser saves the managed object context, which pushes the new entry data down to the SQLite backing store.
Problem in Detail
Let’s assume the host app and the extension are independently parsing an API response for the same API entity at the same time. If both the host app and an extension reach Step 3 before either of them has finished Step 4, then they will both be trying to insert a new Core Data entry for the same uniqueID. When they reach Step 4 and call
save: on their respective managed object contexts, Core Data will happily create duplicate entries.
As far as I’m aware, Core Data doesn’t have any way to mark an attribute as unique. I need a Core Data equivalent to a SQLite
INSERT OR IGNORE +
UPDATE combo.. Or else I need a way to “lock” the persistent store’s SQLite backing store, which sounds like a recipe for trouble.
Is there a known approach to this rather novel problem introduced by iOS 8 extensions? If you have an answer, please consider posting it on Stack Overflow.