Debugging Push Notifications on iOS
The real value/uniqueness on this page is showing how to use Console.app for push notifications. I also include some more basic guidance to help you avoid some common problems.
Use the debugger
Launch the application from Xcode (run, but ensure your scheme uses the Debug build configuration, not release or profile) or AppCode (debug mode). Place breakpoints in methods that should be called to confirm that your methods are (not) being called. Some useful methods:
- Alert notification:
- Note: If you send a message without
data
, you won’t be able to handle the notification indidReceiveRemoteNotification
below. Instead, you can handle notification taps by the user, and decide to even show it if the app is in the foreground. - Notification tap:
didReceiveNotificationResponse:completionHandler:
- Foreground alert notifications:
userNotificationCenter(_:willPresent:withCompletionHandler:)
- Note: If you send a message without
- Background notifications:
application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
- Don’t use
application(_:didReceiveRemoteNotification:)
, the deprecated version.
- Don’t use
Request authorization from the user
If showing an alert notification to the user, be sure to requestAuthorization
to show notifications from the user. This is not required if you just want background messages, handling messages in didReceive(_:completionHandler:)
. It’s really easy to forget this.
Try sending it directly to APNs
You should do this when first debugging. This allows you to test push notifications without having to structure a message correctly, since this file does it for you. This also reduces the complexity/ layers in the problem when first debugging, since 3rd party services (Ably’s Channels or Firebase Cloud Messaging). If the device is registered correctly, it should receive this message. Make sure to update the TEAMID
, KEYID
, SECRET
, BUNDLEID
and DEVICETOKEN
. My colleague shared this script with me:
Usage: Get direct_apns.sh
from this GitHub Gist. Run chmod +x direct_apns.sh
and execute it: ./direct_apns.sh
.
- If the script failed with an error, check the error which is returned by APNs.
- If the script was successful but it doesn’t appear to arrive on the device, time to read the logs. (See below)
- If this arrives on the device, but your own messages don’t, this means you are not structuring your message correctly. For example, I’ve seen users who set
apns-headers
in Firebase at the wrong level of the JSON being sent to Firebase.
Reading device logs
Open Console.app (this is different to Terminal.app or iTerm2.app) installed on your mac. To confirm your application received the push message/ check for errors related to push notifications:
Be sure to start logging for the correct dvice. Then find relevant logs by:
- search for the following log messages:
- Both failures and success:
com.apple.pushLaunch
- Failures only:
CANCELED: com.apple.pushLaunch
. For example, this may show the log line:CANCELED: com.apple.pushLaunch.com.example.app:DBA43D at priority 10
- Success only:
COMPLETED com.apple.pushLaunch
. For example, this may show the log lineCOMPLETED com.apple.pushLaunch.package_name:XXXXXX at priority 5
- Both failures and success:
- filter for
dasd
process either by right clicking a log line withdasd
and clickShow Process 'dasd'
. - If you are sending a background notification, it may be throttled by iOS. In this case, an error will be shown in Console.app, but will eventually arrive to your application, where
didReceiveRemoteNotification
delegate method will be called, often within a few minutes. If you look in the Console.app logs, you may find sending the exact same message gives different outcomes:ThunderingHerdPolicy
error (What really is theThunderingHerdPolicy
?):
{name: ThunderingHerdPolicy, policyWeight: 1.000, response: {Decision: Must Not Proceed, Score: 0.00, Rationale: [{deviceInUse == 1 AND timeSinceThunderingHerdTriggerEvent < 900}]}} ], FinalDecision: Must Not Proceed}
cameraIsActive
error:
com.apple.pushLaunch.io.ably.flutter.plugin-example:4935F4:[ {name: MemoryPressurePolicy, policyWeight: 5.000, response: {Decision: Must Not Proceed, Score: 0.00, Rationale: [{cameraIsActive == 1}]}} ], FinalDecision: Must Not Proceed}
- A successful delivery:
{name: ApplicationPolicy, policyWeight: 50.000, response: {Decision: Absolutely Must Proceed, Score: 1.00, Rationale: [{[appIsForeground]: Required:1.00, Observed:1.00},]}} ], FinalDecision: Absolutely Must Proceed}
- Another successful delivery:
com.apple.pushLaunch.io.ably.flutter.plugin-example:5E1C66:[ {name: DeviceActivityPolicy, policyWeight: 5.000, response: {Decision: Can Proceed, Score: 0.25}} ] sumScores:93.270000, denominator:97.020000, FinalDecision: Can Proceed FinalScore: 0.961348}
I initially wrote this to add Push Notifications support to a Flutter library. There might be more useful content there.
A note about environments (APNs sandbox vs production)
There are 2 environments, sandbox and production.
- Sandbox: When running your application via Xcode or your machine (Android Studio, command line), your application runs in either debug, profile or release mode. In all cases, your application will use the sandbox/development APNs environment. Also, If distributing through Development, the sandbox / development APNs environment is used.
- Production: When distributing your app in the App Store, Ad Hoc or through App Store Connect, it will always use the production environment.
More problems?
Comment below with your issues and I’ll try to help. APNs can be quite a frustrating experience, and I understand it a bit deeply now, and I’m happy to help. Or ask a Stack Overflow question and link it below.