How it works
The explanation, with examples you can run.
1. The app registers
Enable the Push Notifications capability (it adds the aps-environment entitlement), ask permission to show alerts, and register on every launch:
final class AppDelegate: NSObject, UIApplicationDelegate { func application(_ app: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { app.registerForRemoteNotifications() // token arrives below; no prompt for this part return true } func application(_ app: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken token: Data) { let hex = token.map { String(format: "%02x", $0) }.joined() Task { try? await API.shared.upload(pushToken: hex) } }} // Separately, before showing alerts:let granted = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound])The token identifies this app on this device. It changes when the app is reinstalled, the device is restored from backup or moved to a new phone, so Apple says not to cache it: ask for it on each launch and send it to your server when it changes. Getting a token doesn't need the user's permission; showing alerts does, and a background push works without it.
2. The provider sends
Your server opens an HTTP/2 connection to api.push.apple.com (or api.sandbox.push.apple.com for development builds) and POSTs JSON to /3/device/<token>. It authenticates either with a token: a JWT signed with ES256 using a .p8 key from your developer account, renewed at least once an hour (but not more often than every 20 minutes), and one key can serve several of your apps; or with a per-app certificate that expires and must be replaced. Useful headers include apns-push-type (alert, background and others), apns-priority, apns-expiration, and apns-collapse-id to replace an earlier notification instead of stacking a new one.
{ "aps": { "alert": { "title": "Order shipped", "body": "Arriving Thursday" }, "badge": 1, "sound": "default" }, "orderID": "A1234"}Your own keys go next to aps, never inside it. The whole payload is limited to 4 KB, so send identifiers, not documents. Delivery is best effort: for an offline device APNs stores one notification per app, usually the latest (for up to 30 days, depending on apns-expiration), it may reorder notifications, and a 410 response means the token is dead and should be deleted.
3. The device handles it
- Alert pushes are shown by the system even if your app isn't running.
UNUserNotificationCenterDelegatetells you when one arrives in the foreground or is tapped. Add"mutable-content": 1and a notification service extension to change the content before display, for example to decrypt it or attach an image. - Background pushes have only
"content-available": 1inaps, with push typebackgroundand priority 5. They wake the app in the background with about 30 seconds of runtime, but are low priority, may be delayed or coalesced, and are throttled if you send more than a few an hour.
On the server side, one domain event ("order shipped") usually fans out to many devices and channels; that is a publish/subscribe problem you solve before you ever call APNs.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.