Push notifications are a way for an app to send information to the user’s phone even when the app isn’t in use. From the user’s perspective this is an extremely valuable feature. It gets information delivered, and does it in near real time. From the developer’s perspective, however, using APNS (Apple Push Notification Service) can sometimes be a bit limiting and clumsy. And with the release of the iOS 7, developers can take advantage of the new push features, but will inevitably stumble upon new challenges.

Here at Layer we’re building the open communications layer for the Internet. Through a simple yet powerful SDK, Layer provides the building blocks for developers to add secure and reliable messaging, voice and video features to any app. Layer provides a globally-distributed, ultra-reliable infrastructure and handles the hard parts—sync, message states, push notifications and more—so that developers can focus on innovation and delighting their users.

Because at Layer we deliver messages in real-time, even when the app is not active, it’s important that we’re able to take the advantage of APNS. Allow me to share my findings and how we tackled Apple’s Push Notifications in LayerKit.

I’d like my data synced, and to go please

First things first. What is LayerKit? It’s what we proudly call our iOS SDK here at Layer. Keeping data synced between the app and the server at all times (or at least as much as possible) is a non trivial task for an iOS developer. We like to think LayerKit is pretty good at it. Our approach uses a special synchronization procedure, which can be triggered internally (within the LayerKit client) or externally (by the server). So whenever there’s any kind of message-related activity (LayerKit receiving or sending messages, or updating their mutable fields) this synchronization procedure is at work. And boy, there’s a lot if it.

We use a custom protocol (which we’ll discuss in a future article) for transferring synchronization data over a persistent TCP connection. Besides that, the protocol also enables receiving of special incoming commands from the server, which trigger the synchronization procedure inside the LayerKit at any time (for example, in response to an incoming message).

TCP socket and LayerKit

TCP socket and LayerKit
It’s all sunshine and rainbows when our app runs in the foreground and has a working connection to the Internet. This is when we have full control (programmatically speaking) over our application and the open TCP socket. In this state LayerKit is always up to date with its messages. But once the app goes into a background state (user switches to another app or locks the screen), things get a bit complicated.

Apple’s Remote Notification Service has been available since iOS 3.0, and we can make great use of it by having our server dispatch remote notifications with short messages. They get pushed inside our iOS device’s notification center and the user can respond by swiping/tapping the notifications. Those particular actions launch the app, as well as notify it that user responded to a certain notification. However, it’s not until the app is fully up and running (in the foreground), establishes a connection to Layer services, and finally completes the synchronization procedure, that it can be considered up to date.

APNS and LayerKit

TCP socket and LayerKit
Apple’s policy does allow for an app to run in a background mode, but unfortunately each mode comes with many trade-offs. One such mode, which is fairly easy to implement, is this: the app is allowed to behave in the background in much the same way as would in the foreground – but it is limited to 10 minutes (or less, depending on iOS’s mood). In this state, we can keep the TCP socket open, trigger the synchronization procedure from the server, and, if we want to alert the user about incoming messages, we can dispatch local notifications i. from within the app.

Local notifications and LayerKit

Local notifications and LayerKit
i. Why local notifications? Because of greater control. We can cancel each individual dispatched local notification, whereas with remote notifications we can only cancel all at once.

Background fetch on remote notification

iOS provides several other background modes that can wake up or keep an app running in a background state, but only if your app uses of any of the following features: Audio and AirPlay, Location updates, Newstand downloads, Voice over IP, External accessory communication, Background fetch, etc… and iOS 7.0 also brings one more addition to this palette: background fetch on remote notification.

In LayerKit, we focus on two kinds of push notifications:

  • push notification – which is a remote notification with an alert string or body
    example payload: {aps: {alert: "hey there, how you doing?"}}
  • push notification with background fetch – which is a remote notification with content-available flag, newly introduced in iOS 7
    example payload: {aps: {content-available: 1}}

Implementing this is not so straightforward, as Apple’s documentation can be a tad confusing at times. Let’s say you wanted to make a test playground app where you could trigger a background fetch upon a received remote notification with a content-available flag. By looking at the XCode project settings (capabilities section) you’d think you need to enable the last two modes, but let’s think about this first.

Background modes

Background modes
There is no detailed information in this screen about these modes, so here’s what may be going through your head: “if I want my application to receive remote notifications, it’s enough to register for them in the code by calling the registerForRemoteNotificationTypes: method. This way my app will be able to receive remote notifications when I implement the application:didReceiveRemoteNotification: delegate method.”

But what are the last two background modes for, anyway? You might think the last one, “Remote notification,” is for receiving plain remote notifications, but why would you need to enable a feature that already works? And the second to last mode, “Background fetch” will let you wake the app up with the ‘content-available’ flag. Right? <Buzz!!> Wrong!

Both modes are indeed background modes, but they really should be named differently. Fortunately, there’s a bit more information on these modes available when looking at your application’s info.plist file. Still, it doesn’t provide accurate information.

info.plist file

‘info.plist’ file
Here’s what these two modes really provide:

  • The first one, called Background fetch (“Item 0” : “fetch”), is a background mode where a smart scheduler wakes your app based on the user’s app usage. It learns the user’s behavior. For example: when and how often does the user actually use the app.
  • The last one, called Remote notification (“Item 1” : “remote-notification”), is a background mode where the app is woken up upon receiving a remote notification with a “content-available” key defined inside the ‘aps’ payload.

Great, we now have a way of waking up the app remotely from the server. But let’s not get our hopes up too soon, because there’s bound to be some trade-offs, right?

  • When the application is woken up remotely, LayerKit only has a 30-second window to complete the synchronization procedure.
  • If the user rebooted his device and never launched the app since the reboot, the app will never wake up remotely.
  • If the user killed the app manually from the app switcher, the app will also never wake up remotely.

Leveraging remote and local notifications

We want to keep the LayerKit client accessible from the server as much as possible since we want to keep it in an up-to-date state as frequently as possible. But we must also be aware of every possible state the app can be in. Here is a checklist of states that are relevant for LayerKit:

Notification states

i. available only for apps that are registered as VoIP apps; it’s where a keepalive handler can be scheduled only once every 600 seconds, and it can only run for 10 seconds
ii. app can run for about 10 minutes in an active background state.
iii. app can receive remote notifications, but alerts aren’t pushed into the device’s notification center.

Let’s go over a few states

Foreground state [G]

Foreground state

App running in foreground state
This timeline demonstrates a perfect scenario, where an app is launched and kept in the foreground state (state G). We don’t need to utilize the remote notification service, since we have a persistent connection to the Layer’s server, and synchronization procedure can be triggered at any time.

But keep in mind: as soon as LayerKit establishes a connection with the Layer Cloud server, it automatically starts the synchronization procedure.

Active background state [F]

As soon as the user presses the home button or locks the device, the app goes into a background state. We can utilize the active-background state (F) and maintain our persistent TCP connection, but only for 10 minutes!

Active background state

App going into active-background state
When the synchronization procedure is complete, we can notify the user of incoming messages by dispatching local notifications.

Inactive background state

After the 10 minute interval, LayerKit drops the connection to Layer’s server, and the application goes into an inactive background (state C). The server can trigger a synchronization request with the help of a remote notification that can set off a background fetch inside the app. The remote notification must have the content-available flag included in the ‘aps’ payload. These notifications are sometimes described as silent remote notifications.

Inactive background state

App going into inactive-background state
Note that in the timeline, the length of some states are exaggerated for the sake of diagram aesthetics.
After the Layer server dispatches the remote notification, it gives the app some time (marked with grey/white dashed lines) to connect to a Layer server. iOS wakes the app up and lets it perform a background fetch for 30 seconds (marked with red/white dashed lines). That should be enough for synchronization procedure to complete. Keep in mind that while the fetch process is in the background we can dispatch local notifications once the synchronization procedure is complete.

App killed or never launched after reboot

Let’s think about the background fetch trade-offs we discussed earlier. If the user killed the app, or if he never launched the app after a device reboot, the app will not wake up in response to a remote notification with ‘content-available’ flag.

App killed state

Dispatching visual remote notifications
This timeline displays how the Layer server tries to trigger a synchronization request, but doesn’t get a connection back from the app in a given period (marked by grey/white dashed lines). After this period, the server marks that app as unreachable, and only dispatches a visual remote notification with an alert text from that moment on. Or at least until the app reconnects to the server.

Note that we always send the content-available flag in the remote notification payload, even if the app failed to respond in any of the previous attempts. One of the scenarios might be that the user launched the app while in a no-cellular or no-WiFi coverage area. As soon as his iOS device finally connects to the Internet, it will receive the push notification and, consequently, wake the app up.

Remote vs local notifications

You might be wondering, why don’t we just settle with remote notifications? Why even bother dispatching local notifications from the app? The answer is simply: we have much better control over local notifications than remote ones.

Suppose the user receives three messages. It doesn’t matter if they’re received separately or in a bunch. The user reads one of those messages on another device, so the message gets marked as read. Surely we’d want the messages that were marked as read to disappear from the notification center.

If those messages are pushed to the user’s device in a form of remote notifications with text, there is no way to cancel individual ones from the notification center. The server can only cancel all remote notifications at once.

Notifications remote

Dispatching visual remote notifications and then cancelling them all at once
This is achieved by dispatching a remote notification with a badge value set to zero:
{aps: {badge: 0}}

Now let’s see what we can do with local notifications:

Notifications local

Presenting local notifications and then cancelling a specific one

  1. an app in the background receives a sync-request,
  2. after the synchronization process, it receives three unread messages,
  3. app presents three different local notifications and marks them with special identifiersi.,
  4. app receives another sync request,
  5. after the synchronization process completes, the app notices one of the messages has been marked as read, so it removes it from the notification center.

i. we can add any kind of information to local notifications by utilizing the userInfo dictionary.

There are some other controls we have on the server-side over remote notifications. We can specify an expiration time for each notification – how long the message should exist in APNS’ queue – if the user’s device is unreachable. One other thing you can specify is priority. APNS can deal with two kinds of priorities, one which dispatches the notification immediately (but doesn’t work with the ‘content-available’ flag), and another (lowest) which dispatches the notification when it’s best (to preserve device’s battery).

Conclusion

Apple’s notification system is a very powerful tool. It comes with a robust but somewhat confusing API. As a developer, you can solve a lot of real-time notification scenarios with it, but only if you’re able to figure out how to leverage it properly and can afford to support it (it’s an ever-changing territory). At Layer, we believe application developers shouldn’t be wasting time solving the problems described above. Reinventing the wheel over and over again takes time away that can be better spent. We want application developers to focus on making apps with great design and polished user experiences. Secure and reliable messaging, voice and video features – we got that covered.

Want to help us build the open communications layer for the internet? Join us.