[Sponsor] PagerDuty
PagerDuty is an operations performance platform that sits at the heart of your systems and helps you resolve incidents faster. Easily integrate all your monitoring tools for complete visibility across operations, and create on-call schedules to assign the right people to be alerted when something breaks. You can respond to alerts from anywhere, get automatic incident escalations, and ultimately shorten your response time to solve problems faster.
This post is sponsored via Syndicate Ads.
All These App Rejection Stories in One Headline “No One Dares to Build WatchKit Apps that Matter”
In a tweet so ludicrous it bears quoting in full, Greg Pierce announces that Apple has yet again rejected the Drafts.app Today Widget:
Last week’s rejection? Your Today widget does too much.
This week’s? Your Today widget doesn’t do enough.
Seriously.
— Greg Pierce (@agiletortoise) December 9, 2014
Every time one of these iOS 8 Extension rejection stories tears across my Twitter and Bloglovin feeds, my mind goes straight to one thing: Apple Watch.
If you’re not already aware, the first round of third-party apps for Apple Watch will be built using APIs similar to those used to make Today Widget extensions. The Watch apps will bear one other crucial similarity to Today Widgets: they’re brand new.
I fear that Watch apps will be subject to the same capricious rejections and inscrutable policies that are already spoiling what should be a renaissance of fresh ideas. Between the lack of sustainable productivity app pricing (subscriptions, trials, paid upgrades) and the unpredictable threat of an App Review rejection, I personally can’t summon any excitement whatsoever about WatchKit. Why should I give a shit about sassy new hardware if the only apps worth building either can’t make money or might get rejected?
Unless something changes at Apple, I predict that we’ll mostly see two kinds of Watch apps next year: hobby apps by curious solo developers, and play-it-safe, free apps for business like Twitter or eBay. And it will be a shame, because the Apple Watch will deserve better.
[Sponsor]: PagerDuty
PagerDuty is an operations performance platform that sits at the heart of your systems and helps you resolve incidents faster. Easily integrate all your monitoring tools for complete visibility across operations, and create on-call schedules to assign the right people to be alerted when something breaks. You can respond to alerts from anywhere, get automatic incident escalations, and ultimately shorten your response time to solve problems faster.
This post is sponsored via Syndicate Ads.
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
NSFileCoordinatorto synchronize read/write access to shared file resources. However, in a bizarre technical note they also claim that usingNSFileCoordinatorin an extension can lead to unpreventable deadlocks and should not be used. If Apple ever resolves this issue, usingNSFileCoordinatormight 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.
WatchKit Extension Problem: Sharing a Core Data Store Can Lead to Duplicate Entries
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:
General Setup
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.