Real-time inter-app communication
Or more like “real-time”, (near)real-time or whatever modifier you prefer.
App Extensions are the new shiny in iOS 8 and one that interest me the most, especially in the fitness area. Here’s why: I use my iPhone as the core device of my entire business hence I have lots of sensitive personal and business data on it. Thus it’s natural that I have all possible security features turned on. Passcode is set, TouchID is on, instant lock with no grace period etc.
This presents an issue when I go out to run with my iPhone. I don’t usually run with an armband (only in races) thus the iPhone is usually in my pocket. From time to time tough, I would like to check the parameters - heart rate, distance, pace etc. In reality, this means that I should:
- dug it out from the pocket
- unlock – Touch ID is not always reliable with sweaty fingers (at least on 5s), so it means type out the passcode
- back into the pocket
Step no. 2 is what bugs me and where extensions come into play. If I have to slide to the left for the passcode screen, then I might as well slide from the top and get the info I need from the Today widget.
That’s the thought process that ended with: iOS 8 version of Run 5k will have Today widget that’s (more or less) a replica of its running view.
The only problem to solve is - how to communicate app status between the main app and the widget, in real time?
Idea 1
NSExtensionContext
seems like a proper place to give us means to shuttle data and state back and forth, but it’s a non-starter. It’s simple and very limited class and there’s nothing in it to allow real-time communication.
Maybe in iOS 9.
Idea 2
File-based, on disk. Main app would write data into it, widget would read from it. However, widget might also need to write some data, for example to alter running state: I may want to temporarily pause the run.
Thus you need to carefully coordinate reads/writes and to resolve the potential lock-ups. Given this and also the fact that it’s a file I/O, the real-time may become far less real then I would prefer and it could actually lead to widget lagging behind actual app state.
I may be wrong and it could turn out to be performant enough. Would love to hear about it, if you have experiences.
Idea 3
App-specific UIPasteboard
. It’s all in memory, so no slow I/O to speak of. Idea is that I would write my items into the pasteboard from the app, widget will read them. There is no limit what you can place in the pasteboard, but just be careful it’s not a lot of data - memory warnings are not far off in the 1GB RAM devices we still work with.
This is what I chose to go with.
My first approach was whenever the app writes into the pasteboard, UIPasteboardChangedNotification
is fired and I’m using that to trigger widget layout/display. However, even with this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleNotification:)
name:UIPasteboardChangedNotification
object:nil];
Widget’s handler is never called. And that’s really annoying, as I had to resort to polling the UIPasteboard repeatedly. Still testing it out, but I don’t like it, it seems wasteful.
Maybe I’m doing something wrong, still working on it.
Idea 4
Local socket communication..? I have almost non-existent experience with these, so can’t properly investigate. No idea what’s possible to do, will iOS even allow direct communication between two apps of which one is in background. There are a lot of *ix words and terms here that I’m out to become familiar with at some point.
Idea 5
Local web server and client. This is basically a specific implementation of the previous idea, but something that’s known to work in practice, people have these done in their apps.
If the pasteboard becomes non-usable for my use case, I’ll probably try this next.
Call for action
I believe that as iOS matures, direct inter-app communication will become a real need. Apple is making baby steps in that direction, with NSExtensionContext
and App Groups.
The need is here and now it’s the time to formulate the requirements and hit bugreport.apple.com in order to have anything added to the SDK in reasonable time-frame. Just remember the open-source-born Audiobus which even Apple used in their own apps - it’s possible :).
For my use case, a simple key-value storage (NSMutableDictionary
) in NSExtensionContext
would suffice. Thus my first Radar will be to add at least userInfo
property to it, the way NSNotification
has one.
Update, (winning) Idea 6
I can’t believe I completely missed what turned out to be the winning idea: use shared NSUserDefaults
through App Groups. It’s the recommended way to communicate settings between the extension and app and it’s good enough to shuttle back and forth small bits of data.
UIPasteboard
-based solution actually did work, until you lock the screen; locking puts impenetrable barrier and your extension becomes empty. NSUserDefaults works great even when locked, with passcode and all.
Any comments and discussion, please contact me as @radiantav on Twitter.