āļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS Push Notifications 2026: APNs, āđ‚āļ—āđ€āļ„āļ™ āđāļĨāļ° troubleshooting

āļ„āļđāđˆāļĄāļ·āļ­āđ€āļ•āļĢāļĩāļĒāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āļ­āļĒāđˆāļēāļ‡āļ„āļĢāļšāļ–āđ‰āļ§āļ™āđ€āļāļĩāđˆāļĒāļ§āļāļąāļš Push Notifications, APNs, āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ‚āļ—āđ€āļ„āļ™ āđāļĨāļ° troubleshooting āļžāļĢāđ‰āļ­āļĄāļ„āļģāļ–āļēāļĄāļĒāļ­āļ”āļ™āļīāļĒāļĄāđāļĨāļ°āļ„āļģāļ•āļ­āļšāđ‚āļ”āļĒāļĨāļ°āđ€āļ­āļĩāļĒāļ”

āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄ APNs āļ‚āļ­āļ‡ iOS Push Notifications āļžāļĢāđ‰āļ­āļĄāđ‚āļ—āđ€āļ„āļ™āđāļĨāļ° troubleshooting

Push Notifications āļĒāļąāļ‡āđ€āļ›āđ‡āļ™āļŦāļąāļ§āļ‚āđ‰āļ­āļŠāļģāļ„āļąāļāđƒāļ™āļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āļāļēāļĢāđ€āļ‚āđ‰āļēāđƒāļˆāļāļēāļĢāļ—āļģāļ‡āļēāļ™āļ‚āļ­āļ‡ APNs āļāļēāļĢāļˆāļąāļ”āļāļēāļĢ device token āđāļĨāļ°āļāļēāļĢāđāļāđ‰āļ›āļąāļāļŦāļēāļ—āļąāđˆāļ§āđ„āļ›āđāļŠāļ”āļ‡āļ–āļķāļ‡āļ„āļ§āļēāļĄāđ€āļ‚āđ‰āļēāđƒāļˆāļ­āļĒāđˆāļēāļ‡āļĨāļķāļāļ‹āļķāđ‰āļ‡āđƒāļ™āļĢāļ°āļšāļšāļ™āļīāđ€āļ§āļĻāļ‚āļ­āļ‡ Apple āļ„āļđāđˆāļĄāļ·āļ­āļ™āļĩāđ‰āļĢāļ§āļšāļĢāļ§āļĄāļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļ—āļĩāđˆāļžāļšāļšāđˆāļ­āļĒāļ—āļĩāđˆāļŠāļļāļ”

āļˆāļļāļ”āļŠāļģāļ„āļąāļāđƒāļ™āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ

āļœāļđāđ‰āļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļĄāļ­āļ‡āļŦāļēāļœāļđāđ‰āļŠāļĄāļąāļ„āļĢāļ—āļĩāđˆāđ€āļ‚āđ‰āļēāđƒāļˆāļ§āļ‡āļˆāļĢāļŠāļĩāļ§āļīāļ•āļ—āļąāđ‰āļ‡āļŦāļĄāļ”: āļ•āļąāđ‰āļ‡āđāļ•āđˆāļāļēāļĢāļĨāļ‡āļ—āļ°āđ€āļšāļĩāļĒāļ™āļ­āļļāļ›āļāļĢāļ“āđŒāļˆāļ™āļ–āļķāļ‡āļāļēāļĢāļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™ āļĢāļ§āļĄāļ–āļķāļ‡āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ­āļĒāđˆāļēāļ‡āđ€āļŦāļĄāļēāļ°āļŠāļĄāđƒāļ™āđāļ•āđˆāļĨāļ°āļ‚āļąāđ‰āļ™āļ•āļ­āļ™

āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄ APNs: āļĢāļēāļāļāļēāļ™āļ‚āļ­āļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļšāļ™ iOS

Apple Push Notification service (APNs) āļ„āļ·āļ­āļšāļĢāļīāļāļēāļĢāļāļĨāļēāļ‡āļ—āļĩāđˆāļˆāļąāļ”āļāļēāļĢāļāļēāļĢāļŠāđˆāļ‡ push notification āđ„āļ›āļĒāļąāļ‡āļ­āļļāļ›āļāļĢāļ“āđŒāļ‚āļ­āļ‡ Apple āļāļēāļĢāđ€āļ‚āđ‰āļēāđƒāļˆāļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāļ‚āļ­āļ‡āļĄāļąāļ™āļˆāļģāđ€āļ›āđ‡āļ™āļ•āđˆāļ­āļāļēāļĢāļ•āļ­āļšāļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āļĄāļĩāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž

APNs āļ—āļģāļ‡āļēāļ™āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļāļēāļĢāļŠāļ·āđˆāļ­āļŠāļēāļĢāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡āļāļąāļšāļŠāļēāļĄāļœāļđāđ‰āđ€āļĨāđˆāļ™āļŦāļĨāļąāļ: āđāļ­āļ› iOS, APNs āđāļĨāļ°āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ backend āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđ€āļ›āđ‡āļ™āļ”āļąāļ‡āļ™āļĩāđ‰:

AppDelegate.swiftswift
import UIKit
import UserNotifications

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // Request notification authorization
        UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert, .badge, .sound]
        ) { granted, error in
            guard granted else { return }
            // Register with APNs
            DispatchQueue.main.async {
                application.registerForRemoteNotifications()
            }
        }
        return true
    }
}

āļāļēāļĢāļĨāļ‡āļ—āļ°āđ€āļšāļĩāļĒāļ™ APNs āđ€āļāļīāļ”āļ‚āļķāđ‰āļ™āđƒāļ™āļŠāļ­āļ‡āļ‚āļąāđ‰āļ™: āļ‚āļ­āļ­āļ™āļļāļāļēāļ•āļˆāļēāļāļœāļđāđ‰āđƒāļŠāđ‰ āļˆāļēāļāļ™āļąāđ‰āļ™āđ€āļĢāļĩāļĒāļ registerForRemoteNotifications()

āļāļēāļĢāļˆāļąāļ”āļāļēāļĢ device token

Device token āđ€āļ›āđ‡āļ™āļ•āļąāļ§āļĢāļ°āļšāļļāđ€āļ‰āļžāļēāļ°āļ—āļĩāđˆ APNs āļŠāļĢāđ‰āļēāļ‡āļ‚āļķāđ‰āļ™āđ€āļžāļ·āđˆāļ­āđƒāļŠāđ‰āļāļąāļšāļ­āļļāļ›āļāļĢāļ“āđŒāđ€āļ„āļĢāļ·āđˆāļ­āļ‡āđƒāļ”āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļ™āļķāđˆāļ‡ āđ‚āļ—āđ€āļ„āļ™āļ™āļĩāđ‰āļŠāļēāļĄāļēāļĢāļ–āđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡āđ„āļ”āđ‰ āđāļĨāļ°āļ„āļ§āļĢāļ–āļđāļāļŠāđˆāļ‡āđ„āļ›āļĒāļąāļ‡āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ backend āļ—āļļāļāļ„āļĢāļąāđ‰āļ‡āļ—āļĩāđˆāđ€āļ›āļīāļ”āđāļ­āļ›

AppDelegate.swiftswift
extension AppDelegate {

    // Callback when APNs provides the token
    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        // Convert token to hexadecimal string
        let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
        print("Device Token: \(tokenString)")

        // Send to backend server
        sendTokenToServer(tokenString)
    }

    // Callback on registration failure
    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        print("Failed to register: \(error.localizedDescription)")
    }

    private func sendTokenToServer(_ token: String) {
        // Implement HTTP request to backend
    }
}

āđ‚āļ—āđ€āļ„āļ™āļ–āļđāļāļŠāđˆāļ‡āļāļĨāļąāļšāļĄāļēāđƒāļ™āļĢāļđāļ›āļ‚āļ­āļ‡ Data āđāļĨāļ°āļ•āđ‰āļ­āļ‡āļ–āļđāļāđāļ›āļĨāļ‡āđ€āļ›āđ‡āļ™āļŠāļ•āļĢāļīāļ‡āđ€āļĨāļ‚āļāļēāļ™āļŠāļīāļšāļŦāļāļāđˆāļ­āļ™āļŠāđˆāļ‡āđ„āļ›āļĒāļąāļ‡āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ

āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ APNs āļ—āļĩāđˆāļžāļšāļšāđˆāļ­āļĒ

āļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āļĄāļąāļāļĄāļĩāļ—āļąāđ‰āļ‡āļ„āļģāļ–āļēāļĄāļ—āļĪāļĐāļŽāļĩāđāļĨāļ°āļ›āļāļīāļšāļąāļ•āļīāđ€āļāļĩāđˆāļĒāļ§āļāļąāļš APNs āļ•āđˆāļ­āđ„āļ›āļ™āļĩāđ‰āļ„āļ·āļ­āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆāļžāļšāļšāđˆāļ­āļĒāļ—āļĩāđˆāļŠāļļāļ”āļžāļĢāđ‰āļ­āļĄāļ„āļģāļ•āļ­āļšāđ‚āļ”āļĒāļĨāļ°āđ€āļ­āļĩāļĒāļ”

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 1: āļ„āļ§āļēāļĄāđāļ•āļāļ•āđˆāļēāļ‡āļĢāļ°āļŦāļ§āđˆāļēāļ‡ APNs sandbox āļāļąāļš production āļ„āļ·āļ­āļ­āļ°āđ„āļĢ?

āļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄāļ‚āļ­āļ‡ APNs

APNs āļĄāļĩāļŠāļ­āļ‡āļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄāđāļĒāļāļāļąāļ™āđ‚āļ”āļĒāļĄāļĩ endpoint āļ•āđˆāļēāļ‡āļāļąāļ™ āđ‚āļ—āđ€āļ„āļ™āļ—āļĩāđˆāļŠāļĢāđ‰āļēāļ‡āđƒāļ™āļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄāļŦāļ™āļķāđˆāļ‡āļˆāļ°āđƒāļŠāđ‰āđƒāļ™āļ­āļĩāļāļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄāđ„āļĄāđˆāđ„āļ”āđ‰

| āļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄ | Endpoint | āļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™ | |--------------|----------|-----------| | Sandbox | api.sandbox.push.apple.com | Debug, TestFlight | | Production | api.push.apple.com | App Store |

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 2: āļˆāļąāļ”āļāļēāļĢāļāļąāļšāđ‚āļ—āđ€āļ„āļ™āļŦāļĄāļ”āļ­āļēāļĒāļļāļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

Device token āļ­āļēāļˆāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđ„āļ”āđ‰āļ”āđ‰āļ§āļĒāļŦāļĨāļēāļĒāđ€āļŦāļ•āļļāļœāļĨ: āļāļēāļĢāļ„āļ·āļ™āļ„āđˆāļēāļĢāļ°āļšāļš āļāļēāļĢāļ•āļīāļ”āļ•āļąāđ‰āļ‡āļšāļ™āļ­āļļāļ›āļāļĢāļ“āđŒāđƒāļŦāļĄāđˆ āļŦāļĢāļ·āļ­āļāļēāļĢāļĢāļĩāđ€āļŸāļĢāļŠāđ€āļ›āđ‡āļ™āļĢāļ°āļĒāļ°āđ‚āļ”āļĒ APNs āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļ•āđ‰āļ­āļ‡āļˆāļąāļ”āļāļēāļĢāļāļąāļšāļāļēāļĢāļ•āļ­āļšāļāļĨāļąāļšāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ‚āļ­āļ‡ APNs āļ­āļĒāđˆāļēāļ‡āđ€āļŦāļĄāļēāļ°āļŠāļĄ

NotificationService.swiftswift
enum APNsError: Int {
    case badDeviceToken = 400
    case unregistered = 410
    case payloadTooLarge = 413
    case tooManyRequests = 429
    case internalServerError = 500

    var shouldRemoveToken: Bool {
        // Remove token only if device is no longer registered
        return self == .unregistered || self == .badDeviceToken
    }
}

struct APNsResponse {
    let statusCode: Int
    let deviceToken: String

    func handleError() {
        guard let error = APNsError(rawValue: statusCode) else { return }

        if error.shouldRemoveToken {
            // Remove token from database
            TokenRepository.shared.remove(deviceToken)
        }
    }
}

āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ” APNs āļāļąāđˆāļ‡āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļĄāļĩāļ„āļ§āļēāļĄāļŠāļģāļ„āļąāļāļ­āļĒāđˆāļēāļ‡āļĒāļīāđˆāļ‡āļ•āđˆāļ­āļāļēāļĢāļĢāļąāļāļĐāļēāļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨāđ‚āļ—āđ€āļ„āļ™āđƒāļŦāđ‰āļŠāļ°āļ­āļēāļ”āđāļĨāļ°āļŦāļĨāļĩāļāđ€āļĨāļĩāđˆāļĒāļ‡āļ„āļģāļ‚āļ­āļ—āļĩāđˆāđ„āļĄāđˆāļˆāļģāđ€āļ›āđ‡āļ™

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 3: āđƒāļŠāđ‰āļ‡āļēāļ™ silent notification āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

Silent notification āļŠāđˆāļ§āļĒāļ›āļĨāļļāļāđāļ­āļ›āđƒāļŦāđ‰āļ—āļģāļ‡āļēāļ™āļžāļ·āđ‰āļ™āļŦāļĨāļąāļ‡āđ€āļžāļ·āđˆāļ­āļ”āļģāđ€āļ™āļīāļ™āļ‡āļēāļ™āđ‚āļ”āļĒāđ„āļĄāđˆāđāļŠāļ”āļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđƒāļ” āđ† āļ•āđˆāļ­āļœāļđāđ‰āđƒāļŠāđ‰

AppDelegate.swiftswift
func application(
    _ application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable: Any],
    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
    // Check if this is a silent notification
    guard let aps = userInfo["aps"] as? [String: Any],
          aps["content-available"] as? Int == 1 else {
        completionHandler(.noData)
        return
    }

    // Perform background task
    performBackgroundTask { result in
        switch result {
        case .success:
            completionHandler(.newData)
        case .failure:
            completionHandler(.failed)
        }
    }
}

Payload JSON āļ‚āļ­āļ‡ silent notification āļ•āđ‰āļ­āļ‡āļĄāļĩ "content-available": 1 āļ āļēāļĒāđƒāļ™āļ­āļ­āļšāđ€āļˆāđ‡āļāļ•āđŒ aps

āļžāļĢāđ‰āļ­āļĄāļ—āļĩāđˆāļˆāļ°āļžāļīāļŠāļīāļ•āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āđāļĨāđ‰āļ§āļŦāļĢāļ·āļ­āļĒāļąāļ‡āļ„āļĢāļąāļš?

āļāļķāļāļāļ™āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āđāļšāļšāđ‚āļ•āđ‰āļ•āļ­āļš, flashcards āđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

Notification Service Extension: āļāļēāļĢāļ›āļĢāļąāļšāđāļ•āđˆāļ‡āļ‚āļąāđ‰āļ™āļŠāļđāļ‡

Notification Service Extension āļ­āļ™āļļāļāļēāļ•āđƒāļŦāđ‰āđāļāđ‰āđ„āļ‚āđ€āļ™āļ·āđ‰āļ­āļŦāļēāļ‚āļ­āļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļāđˆāļ­āļ™āđāļŠāļ”āļ‡āļœāļĨ āļŸāļĩāđ€āļˆāļ­āļĢāđŒāļ™āļĩāđ‰āļĄāļąāļāļ–āļđāļāļ–āļēāļĄāđƒāļ™āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ

āļŠāļĢāđ‰āļēāļ‡ Service Extension

NotificationService.swift (in Extension target)swift
import UserNotifications

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        self.contentHandler = contentHandler
        bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent

        guard let bestAttemptContent = bestAttemptContent else {
            contentHandler(request.content)
            return
        }

        // Modify content
        if let imageURLString = bestAttemptContent.userInfo["image-url"] as? String,
           let imageURL = URL(string: imageURLString) {
            downloadImage(from: imageURL) { attachment in
                if let attachment = attachment {
                    bestAttemptContent.attachments = [attachment]
                }
                contentHandler(bestAttemptContent)
            }
        } else {
            contentHandler(bestAttemptContent)
        }
    }

    override func serviceExtensionTimeWillExpire() {
        // Called when time limit (30 seconds) is exceeded
        if let contentHandler = contentHandler,
           let bestAttemptContent = bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }

    private func downloadImage(
        from url: URL,
        completion: @escaping (UNNotificationAttachment?) -> Void
    ) {
        URLSession.shared.downloadTask(with: url) { localURL, _, error in
            guard let localURL = localURL, error == nil else {
                completion(nil)
                return
            }

            let tempDirectory = FileManager.default.temporaryDirectory
            let fileName = url.lastPathComponent
            let destinationURL = tempDirectory.appendingPathComponent(fileName)

            try? FileManager.default.moveItem(at: localURL, to: destinationURL)

            let attachment = try? UNNotificationAttachment(
                identifier: fileName,
                url: destinationURL,
                options: nil
            )
            completion(attachment)
        }.resume()
    }
}

Extension āļĄāļĩāđ€āļ§āļĨāļē 30 āļ§āļīāļ™āļēāļ—āļĩāđƒāļ™āļāļēāļĢāđāļāđ‰āđ„āļ‚āđ€āļ™āļ·āđ‰āļ­āļŦāļē āļŦāļēāļāđ€āļāļīāļ™āļ‚āļĩāļ”āļˆāļģāļāļąāļ”āļ™āļĩāđ‰āļˆāļ°āļĄāļĩāļāļēāļĢāđ€āļĢāļĩāļĒāļāđ€āļĄāļ˜āļ­āļ” serviceExtensionTimeWillExpire()

Troubleshooting Push Notifications

āļāļēāļĢāļ”āļĩāļšāļąāļ push notification āđ€āļ›āđ‡āļ™āļŦāļąāļ§āļ‚āđ‰āļ­āļ—āļĩāđˆāļžāļšāļ‹āđ‰āļģāđƒāļ™āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ āļœāļđāđ‰āļŠāļĄāļąāļ„āļĢāļ„āļ§āļĢāļĢāļđāđ‰āļˆāļąāļāđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­āđāļĨāļ°āđ€āļ—āļ„āļ™āļīāļ„āđƒāļ™āļāļēāļĢāļ§āļīāļ™āļīāļˆāļ‰āļąāļĒ

āļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļ–āļēāļ™āļ°āļāļēāļĢāļĨāļ‡āļ—āļ°āđ€āļšāļĩāļĒāļ™

DebugHelper.swiftswift
struct PushNotificationDebugger {

    static func checkNotificationStatus() async {
        let center = UNUserNotificationCenter.current()
        let settings = await center.notificationSettings()

        print("=== Push Notification Status ===")
        print("Authorization: \(settings.authorizationStatus.description)")
        print("Alert: \(settings.alertSetting.description)")
        print("Badge: \(settings.badgeSetting.description)")
        print("Sound: \(settings.soundSetting.description)")
        print("Notification Center: \(settings.notificationCenterSetting.description)")
    }
}

extension UNAuthorizationStatus {
    var description: String {
        switch self {
        case .notDetermined: return "Not Determined"
        case .denied: return "Denied"
        case .authorized: return "Authorized"
        case .provisional: return "Provisional"
        case .ephemeral: return "Ephemeral"
        @unknown default: return "Unknown"
        }
    }
}

āļŸāļąāļ‡āļāđŒāļŠāļąāļ™ debug āļ™āļĩāđ‰āļŠāđˆāļ§āļĒāļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļ–āļēāļ™āļ°āļŠāļīāļ—āļ˜āļīāđŒāļ‚āļ­āļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āļĢāļ§āļ”āđ€āļĢāđ‡āļ§

āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ—āļĩāđˆāļžāļšāļšāđˆāļ­āļĒāđāļĨāļ°āļ§āļīāļ˜āļĩāđāļāđ‰

āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ—āļĩāđˆāļ„āļ§āļĢāļĢāļđāđ‰

āļœāļđāđ‰āļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļĄāļąāļāļ–āļēāļĄāđ€āļāļĩāđˆāļĒāļ§āļāļąāļšāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ—āļĩāđˆāļžāļšāļšāđˆāļ­āļĒāđāļĨāļ°āđāļ™āļ§āļ—āļēāļ‡āđāļāđ‰āđ„āļ‚ āļ•āđˆāļ­āđ„āļ›āļ™āļĩāđ‰āļ„āļ·āļ­āļŠāļīāđˆāļ‡āļŠāļģāļ„āļąāļāļ—āļĩāđˆāļŠāļļāļ”āļ—āļĩāđˆāļ„āļ§āļĢāļˆāļ”āļˆāļģ

| āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ” | āļŠāļēāđ€āļŦāļ•āļļ | āļ§āļīāļ˜āļĩāđāļāđ‰ | |-------------|-------|--------| | āđ‚āļ—āđ€āļ„āļ™āđ„āļĄāđˆāļ–āļđāļāļ•āđ‰āļ­āļ‡ | āļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄāļœāļīāļ” (sandbox/prod) | āļ•āļĢāļ§āļˆāļŠāļ­āļš provisioning profile | | āđ„āļĄāđˆāđ„āļ”āđ‰āļĢāļąāļšāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™ | āđ€āļ›āļīāļ”āđ‚āļŦāļĄāļ”āļ›āļĢāļ°āļŦāļĒāļąāļ”āļžāļĨāļąāļ‡āļ‡āļēāļ™ | āļ—āļ”āļŠāļ­āļšāļ‚āļ“āļ°āđāļšāļ•āđ€āļ•āđ‡āļĄ | | Extension āđ„āļĄāđˆāļ–āļđāļāđ€āļĢāļĩāļĒāļ | Payload āđ„āļĄāđˆāļĄāļĩ mutable-content | āđ€āļžāļīāđˆāļĄ "mutable-content": 1 | | Background fetch āļĨāđ‰āļĄāđ€āļŦāļĨāļ§ | āļœāļđāđ‰āđƒāļŠāđ‰āļ›āļīāļ”āđāļ­āļ›āđ€āļ­āļ‡ | āđāļˆāđ‰āļ‡āļœāļđāđ‰āđƒāļŠāđ‰āđ€āļāļĩāđˆāļĒāļ§āļāļąāļšāļ‚āđ‰āļ­āļˆāļģāļāļąāļ” |

āļ—āļ”āļŠāļ­āļš APNs āđ‚āļ”āļĒāļ•āļĢāļ‡

āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ—āļ”āļŠāļ­āļšāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļĢāļ°āļŦāļ§āđˆāļēāļ‡āļžāļąāļ’āļ™āļē āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­ curl āļŠāđˆāļ§āļĒāđƒāļŦāđ‰āļŠāđˆāļ‡āļ„āļģāļ‚āļ­āđ„āļ›āļ—āļĩāđˆ APNs āđ„āļ”āđ‰āđ‚āļ”āļĒāļ•āļĢāļ‡:

TestPayload.swiftswift
struct APNsTestPayload {

    static let silentNotification = """
    {
        "aps": {
            "content-available": 1
        },
        "custom-data": "test"
    }
    """

    static let richNotification = """
    {
        "aps": {
            "alert": {
                "title": "New message",
                "body": "Message content"
            },
            "mutable-content": 1,
            "sound": "default"
        },
        "image-url": "https://example.com/image.jpg"
    }
    """
}

Payload āļ—āļ”āļŠāļ­āļšāđ€āļŦāļĨāđˆāļēāļ™āļĩāđ‰āļŠāđˆāļ§āļĒāļ•āļĢāļ§āļˆāļŠāļ­āļšāļžāļĪāļ•āļīāļāļĢāļĢāļĄāļ‚āļ­āļ‡āđāļ­āļ›āļāļąāļšāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļŦāļĨāļēāļĒāļ›āļĢāļ°āđ€āļ āļ—

āđāļ™āļ§āļ›āļāļīāļšāļąāļ•āļīāļ—āļĩāđˆāļ”āļĩāđƒāļ™āļĢāļ°āļ”āļąāļš production

āļ„āļģāļ–āļēāļĄāđ€āļāļĩāđˆāļĒāļ§āļāļąāļšāđāļ™āļ§āļ›āļāļīāļšāļąāļ•āļīāļ—āļĩāđˆāļ”āļĩāļŠāđˆāļ§āļĒāļ›āļĢāļ°āđ€āļĄāļīāļ™āļ›āļĢāļ°āļŠāļšāļāļēāļĢāļ“āđŒāļ‚āļ­āļ‡āļœāļđāđ‰āļŠāļĄāļąāļ„āļĢāļāļąāļšāđāļ­āļ› production

āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ‚āļ­āļ‡āđ€āļ„āļĢāļ·āļ­āļ‚āđˆāļēāļĒ

PushTokenManager.swiftswift
actor PushTokenManager {

    private var pendingToken: String?
    private var retryCount = 0
    private let maxRetries = 3

    func registerToken(_ token: String) async {
        pendingToken = token
        await sendTokenWithRetry()
    }

    private func sendTokenWithRetry() async {
        guard let token = pendingToken else { return }

        do {
            try await APIClient.shared.registerPushToken(token)
            pendingToken = nil
            retryCount = 0
        } catch {
            retryCount += 1
            if retryCount < maxRetries {
                // Retry with exponential backoff
                let delay = UInt64(pow(2.0, Double(retryCount))) * 1_000_000_000
                try? await Task.sleep(nanoseconds: delay)
                await sendTokenWithRetry()
            }
        }
    }
}

āļāļēāļĢāđƒāļŠāđ‰ actor āļŠāđˆāļ§āļĒāđƒāļŦāđ‰āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ‚āļ—āđ€āļ„āļ™āļ›āļĨāļ­āļ”āļ āļąāļĒāļ•āđˆāļ­āļāļēāļĢāļ—āļģāļ‡āļēāļ™āļžāļĢāđ‰āļ­āļĄāļāļąāļ™āđƒāļ™āļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄāđāļšāļšāļ‚āļ™āļēāļ™

āļāļēāļĢāđ€āļāđ‡āļšāđ‚āļ—āđ€āļ„āļ™āđāļšāļšāđ‚āļĨāļ„āļąāļĨ

TokenStorage.swiftswift
struct TokenStorage {

    private static let tokenKey = "com.app.pushToken"

    static func save(_ token: String) {
        UserDefaults.standard.set(token, forKey: tokenKey)
    }

    static func retrieve() -> String? {
        UserDefaults.standard.string(forKey: tokenKey)
    }

    static func hasTokenChanged(_ newToken: String) -> Bool {
        guard let savedToken = retrieve() else { return true }
        return savedToken != newToken
    }
}

āļāļēāļĢāļšāļąāļ™āļ—āļķāļāđ‚āļ—āđ€āļ„āļ™āđāļšāļšāđ‚āļĨāļ„āļąāļĨāļŠāđˆāļ§āļĒāļĨāļ”āļāļēāļĢāđ€āļĢāļĩāļĒāļāđ€āļ„āļĢāļ·āļ­āļ‚āđˆāļēāļĒāļ—āļĩāđˆāđ„āļĄāđˆāļˆāļģāđ€āļ›āđ‡āļ™āđ€āļĄāļ·āđˆāļ­āđ‚āļ—āđ€āļ„āļ™āļĒāļąāļ‡āđ„āļĄāđˆāđ€āļ›āļĨāļĩāđˆāļĒāļ™

āļŠāļĢāļļāļ›

āļ„āļ§āļēāļĄāđ€āļŠāļĩāđˆāļĒāļ§āļŠāļēāļāđƒāļ™ iOS Push Notifications āđāļŠāļ”āļ‡āļ–āļķāļ‡āļ„āļ§āļēāļĄāđ€āļ‚āđ‰āļēāđƒāļˆāļ­āļĒāđˆāļēāļ‡āļĨāļķāļāļ‹āļķāđ‰āļ‡āđƒāļ™āļĢāļ°āļšāļšāļ™āļīāđ€āļ§āļĻāļ‚āļ­āļ‡ Apple āđāļĨāļ°āļāļēāļĢāļŠāļ·āđˆāļ­āļŠāļēāļĢāļĢāļ°āļŦāļ§āđˆāļēāļ‡āđ„āļ„āļĨāđ€āļ­āļ™āļ•āđŒ-āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ āļˆāļļāļ”āļŠāļģāļ„āļąāļāļ—āļĩāđˆāļ„āļ§āļĢāļˆāļģāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ:

✅ āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄ APNs: āđ€āļ‚āđ‰āļēāđƒāļˆāļāļĢāļ°āļšāļ§āļ™āļāļēāļĢāļ•āļąāđ‰āļ‡āđāļ•āđˆāļĨāļ‡āļ—āļ°āđ€āļšāļĩāļĒāļ™āļˆāļ™āļ–āļķāļ‡āļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™

✅ Device token: āļ§āļ‡āļˆāļĢāļŠāļĩāļ§āļīāļ•āđāļĨāļ°āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļĄāļ·āđˆāļ­āđ‚āļ—āđ€āļ„āļ™āđ€āļ›āļĨāļĩāđˆāļĒāļ™

✅ Notification Service Extension: āļ›āļĢāļąāļšāđāļ•āđˆāļ‡āđ€āļ™āļ·āđ‰āļ­āļŦāļēāļ āļēāļĒāđƒāļ™āļāļĢāļ­āļš 30 āļ§āļīāļ™āļēāļ—āļĩ

✅ Troubleshooting: āļĢāļđāđ‰āļˆāļąāļāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ—āļąāđˆāļ§āđ„āļ›āđāļĨāļ°āļ§āļīāļ˜āļĩāđāļāđ‰

✅ Silent notifications: content-available āļŠāļģāļŦāļĢāļąāļš background fetch

✅ āđāļ™āļ§āļ›āļāļīāļšāļąāļ•āļīāļ—āļĩāđˆāļ”āļĩ: āļ•āļĢāļĢāļāļ° retry āļāļēāļĢāđ€āļāđ‡āļšāđ‚āļ—āđ€āļ„āļ™āđāļšāļšāđ‚āļĨāļ„āļąāļĨ āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”

āđ€āļĢāļīāđˆāļĄāļāļķāļāļ‹āđ‰āļ­āļĄāđ€āļĨāļĒ!

āļ—āļ”āļŠāļ­āļšāļ„āļ§āļēāļĄāļĢāļđāđ‰āļ‚āļ­āļ‡āļ„āļļāļ“āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āļŠāļąāļĄāļ āļēāļĐāļ“āđŒāđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āđāļ—āđ‡āļ

#ios
#push-notifications
#apns
#swift
#interview

āđāļŠāļĢāđŒ

āļšāļ—āļ„āļ§āļēāļĄāļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡

āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ iOS StoreKit 2 āđāļĨāļ°āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļšāđ€āļŠāļĢāđ‡āļˆ

āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ StoreKit 2: āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļāđāļĨāļ°āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļšāđ€āļŠāļĢāđ‡āļˆ

āđ€āļŠāļĩāđˆāļĒāļ§āļŠāļēāļāļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āđ€āļāļĩāđˆāļĒāļ§āļāļąāļš StoreKit 2 āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļšāđ€āļŠāļĢāđ‡āļˆ āđāļĨāļ°āļāļēāļĢāļ™āļģāļāļēāļĢāļ‹āļ·āđ‰āļ­āđƒāļ™āđāļ­āļ›āđ„āļ›āđƒāļŠāđ‰ āļžāļĢāđ‰āļ­āļĄāļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āđ‚āļ„āđ‰āļ” Swift āļ—āļĩāđˆāđƒāļŠāđ‰āļ‡āļēāļ™āđ„āļ”āđ‰āļˆāļĢāļīāļ‡

Swift Testing Framework āļžāļĢāđ‰āļ­āļĄāļĄāļēāđ‚āļ„āļĢ #expect āđāļĨāļ° #require āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS

Swift Testing Framework āļŠāļąāļĄāļ āļēāļĐāļ“āđŒ 2026: āļĄāļēāđ‚āļ„āļĢ #expect āđāļĨāļ° #require āđ€āļ—āļĩāļĒāļšāļāļąāļš XCTest

āđ€āļĢāļĩāļĒāļ™āļĢāļđāđ‰ Swift Testing Framework āđƒāļŦāļĄāđˆāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS: āļĄāļēāđ‚āļ„āļĢ #expect āđāļĨāļ° #require āļāļēāļĢāļĒāđ‰āļēāļĒāļˆāļēāļ XCTest āđāļžāļ—āđ€āļ—āļīāļĢāđŒāļ™āļ‚āļąāđ‰āļ™āļŠāļđāļ‡āđāļĨāļ°āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ—āļĩāđˆāļžāļšāļšāđˆāļ­āļĒ

āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ Swift āļŠāļģāļŦāļĢāļąāļšāļ™āļąāļāļžāļąāļ’āļ™āļē iOS

25 āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ Swift āļĒāļ­āļ”āļ™āļīāļĒāļĄāļŠāļģāļŦāļĢāļąāļšāļ™āļąāļāļžāļąāļ’āļ™āļē iOS

āđ€āļ•āļĢāļĩāļĒāļĄāļ•āļąāļ§āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āļ”āđ‰āļ§āļĒ 25 āļ„āļģāļ–āļēāļĄ Swift āļ—āļĩāđˆāļžāļšāļšāđˆāļ­āļĒāļ—āļĩāđˆāļŠāļļāļ”: optionals, closures, ARC, protocols, async/await āđāļĨāļ° pattern āļ‚āļąāđ‰āļ™āļŠāļđāļ‡