Five Reasons CloudKit Notifications Are Not Arriving

Five Reasons CloudKit Notifications Are Not Arriving

CloudKit is a powerful, robust, and scalable solution for mobile and desktop applications. If you want to avoid common pitfalls, you need to understand how the framework runs under the hood. Don't immediately blame the tools if something goes wrong.

A common problem is CloudKit notifications not arriving on one or more devices. When that happens you may wonder who's to blame. Is it Apple? Is it the customer? Or could it be you? Let's find out.

Regular and Silent Push Notifications

It's your application's responsibility to inform CloudKit how to deliver push notifications. You have two options:

  • regular push notifications
  • silent push notifications

What's the difference and how does this impact your application?

Regular Push Notifications

Regular push notifications are the ones most of us know and use day in, day out. A regular push notification notifies the user by displaying a message, making a sound, or badging the application icon. They also show up in Notification Center and on the device's Lock Screen.

Because regular push notifications are visible to the user, applications need to ask the user for permission. I'm sure this code snippet looks familiar. Right?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Register for Remote Notifications
    application.registerForRemoteNotifications()

    return true
}

If you'd like to show the user regular push notifications, you need to request their permission by invoking requestAuthorization(options:completionHandler:). As of iOS 10, tvOS 10, and watchOS 3, you use the User Notifications framework for this.

let userNotificationCenter = UNUserNotificationCenter.current()
userNotificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
    ...
}

A dialog is shown to the user, prompting them for their permission. This isn't new.

A dialog is shown to the user, prompting them for their permission.

It doesn't stop here, though. If she changes her mind, the user can tweak the notification settings for your application in the Settings application.

The user can tweak the notification settings for your application in the Settings application.

Silent Push Notifications

A silent push notification is used to inform an application that there's data available it might be interested in. Silent push notifications are not exclusive to CloudKit, though. You can use them for a wide range of purposes. Apple introduced silent push notifications several years ago to allow Newsstand applications to be notified when new content is available to download.

Your application doesn't need to request the user's explicit permission to receive silent notifications. That's important to keep in mind. Silent push notifications are invisible to the user. If your application only uses silent notifications to be notified by Apple's iCloud servers, then silent notifications are fine.

To receive silent push notifications, an application still needs to register for remote notifications. Don't forget this step.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Register for Remote Notifications
    application.registerForRemoteNotifications()

    return true
}

Before you start debugging an issue related to push notifications, make sure the application(_:didRegisterForRemoteNotificationsWithDeviceToken:) method is invoked. This guarantees that your application was able to register successfully with Apple's APNS servers. Implement application(_:didFailToRegisterForRemoteNotificationsWithError:) to find out why your application wasn't able to register with the APNS servers.

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    ...
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    ...
}

#1: Force Quit

At the time of writing, silent push notification don't arrive if the application has been force quit by the user. If your application is terminated by the user or due to a crash, it no longer receives silent push notifications.

I have to admit that I'm one of those people that force quits applications. Whenever I'm traveling I force quit applications to save a little bit of battery. To be honest, I'm not sure how effective it is. In the past, shortly after the introduction of background modes, it was very effective. Some habits are hard to break.

An application that is force quit no longer receives silent push notifications. It's unclear why Apple made this unfortunate decision as it certainly makes debugging customer issues more difficult. I assume it's a technical limitation. Many people force quit applications out of habit, without realizing it. Keep this in mind when debugging issues related to CloudKit notifications.

#2: Push Notifications Capability

One of the more obvious reasons for a device not receiving notifications is a missing capability. For an application to receive CloudKit notifications, you need to enable the Push Notifications capability in the project's Capabilities tab. Xcode automatically enables this capability when you enable CloudKit in the Capabilities tab. But it doesn't hurt to double-check whether the Push Notifications capability is enabled.

Make sure the Push Notifications capability is enabled.

Apple also recommends to enable two background modes for CloudKit applications:

  • Background fetch
  • Remote notifications

Apple also recommends to enable two background modes, background fetch and remote notifications.)

#3: No Push Notifications On Simulator

If you don't have the luxury of having multiple physical devices to test with, then you may think the simulator is a great alternative. While that's true, know that push notifications are not supported on the simulator.

You need a physical device if you'd like to test push notifications. And if you want to test how changes made on one device are communicated to other devices, then you need multiple physical devices. There's a workaround, though. You can make the changes on the simulator and monitor the updates on a physical device.

#4: Issues Related to CloudKit Subscriptions

A Word About CloudKit Subscriptions

To receive push notifications from CloudKit, you need to create a subscription. While this isn't difficult, there are several details developers often overlook.

User Subscriptions

It's possible that some of your customers receive notifications while others don't. How is that possible and how can you debug this? Subscriptions are tied to users. Every user can create a subscription. This is important to keep in mind when debugging issues related to CloudKit notifications. If a user isn't receiving notifications, it's possible the user's device failed to create a subscription.

Database Subscriptions

Last year, Apple introduced the CKDatabaseSubscription class. This subscription type allows an application to monitor changes in a database. But there are limitations. This subscription type only works for custom zones and, as you may know, custom zones are only supported in the user's private and shared databases.

Due to this limitation, the CKDatabaseSubscription class cannot be used to monitor changes in the public database or changes in the default zone of the private and shared databases.

More Limitations

And it doesn't stop here. You can use the CKQuerySubscription class to monitor changes in the public and private databases. The shared database, however, doesn't support query subscriptions. This means that:

  • you need to use a query subscription to monitor changes in the public database
  • you need to create a custom zone if you'd like to monitor changes in the shared database

Saving Subscriptions

If you don't respect the rules and limitations I mentioned a moment ago, your application won't be able to create a subscription. Saving the subscription simply fails and the device won't receive the updates it was hoping to receive.

Apple emphasizes that error handling should be a fundamental aspect of any CloudKit application. Your application needs to be able to cope with the scenario in which saving a subscription fails. Make sure it can recover from this scenario.

Make Sure There's a Match

Another common reason a CloudKit notification isn't arriving is simply because Apple's iCloud servers didn't send one. Your application may be expecting an update that isn't coming. Did you add that new record to the custom zone you created or did you accidentally added it to the default zone of the user's private database? Is the update you made to that record matched by the predicate of the query subscription your application created? These are important considerations.

If you're working with query subscriptions, Apple recommends to use a true predicate to make sure the problem isn't related to the predicate of the query subscription. Once you've found the issue, you can update the query subscription with the correct predicate.

#5: Delivery Problems

You probably know that Apple doesn't guarantee the delivery of push notifications. The company describes the delivery mechanism of push notifications as "best effort". There's no guarantee of delivery and no foolproof solution to determine that a push notification has reached its destination.

A push notification can get lost in limbo, never arriving at its destination. But there's more. Apple's servers coalesce multiple notifications if they arrive in a short period of time. This means that not every notification is sent to its destination if the APNS servers decide otherwise.

Last but not least, if a device is unreachable, Apple's servers store the push notifications for the device. Did I write notifications? I meant notification. Singular. Only the last notification is stored by the APNS servers and sent to its destination when possible. This means that it's possible that some notifications never reach their destination. And if the APNS servers are not able to deliver the cached notification in a timely manner, it is discarded.

Debugging

Debugging issues related to CloudKit notifications can be frustrating, but I hope these tips can help you eliminate and resolve common problems. The APNS infrastructure is mature and robust. It's wise to not immediately point a finger to Apple when a notification doesn't arrive. Take a close look at your code, your application's configuration, and the devices you're testing with.