āļŠāļąāļĄāļ āļēāļĐāļāđ iOS Push Notifications 2026: APNs, āđāļāđāļāļ āđāļĨāļ° troubleshooting
āļāļđāđāļĄāļ·āļāđāļāļĢāļĩāļĒāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ iOS āļāļĒāđāļēāļāļāļĢāļāļāđāļ§āļāđāļāļĩāđāļĒāļ§āļāļąāļ Push Notifications, APNs, āļāļēāļĢāļāļąāļāļāļēāļĢāđāļāđāļāļ āđāļĨāļ° troubleshooting āļāļĢāđāļāļĄāļāļģāļāļēāļĄāļĒāļāļāļāļīāļĒāļĄāđāļĨāļ°āļāļģāļāļāļāđāļāļĒāļĨāļ°āđāļāļĩāļĒāļ

Push Notifications āļĒāļąāļāđāļāđāļāļŦāļąāļ§āļāđāļāļŠāļģāļāļąāļāđāļāļŠāļąāļĄāļ āļēāļĐāļāđ iOS āļāļēāļĢāđāļāđāļēāđāļāļāļēāļĢāļāļģāļāļēāļāļāļāļ APNs āļāļēāļĢāļāļąāļāļāļēāļĢ device token āđāļĨāļ°āļāļēāļĢāđāļāđāļāļąāļāļŦāļēāļāļąāđāļ§āđāļāđāļŠāļāļāļāļķāļāļāļ§āļēāļĄāđāļāđāļēāđāļāļāļĒāđāļēāļāļĨāļķāļāļāļķāđāļāđāļāļĢāļ°āļāļāļāļīāđāļ§āļĻāļāļāļ Apple āļāļđāđāļĄāļ·āļāļāļĩāđāļĢāļ§āļāļĢāļ§āļĄāļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđāļāļĩāđāļāļāļāđāļāļĒāļāļĩāđāļŠāļļāļ
āļāļđāđāļŠāļąāļĄāļ āļēāļĐāļāđāļĄāļāļāļŦāļēāļāļđāđāļŠāļĄāļąāļāļĢāļāļĩāđāđāļāđāļēāđāļāļ§āļāļāļĢāļāļĩāļ§āļīāļāļāļąāđāļāļŦāļĄāļ: āļāļąāđāļāđāļāđāļāļēāļĢāļĨāļāļāļ°āđāļāļĩāļĒāļāļāļļāļāļāļĢāļāđāļāļāļāļķāļāļāļēāļĢāļŠāđāļāļāļēāļĢāđāļāđāļāđāļāļ·āļāļ āļĢāļ§āļĄāļāļķāļāļāļēāļĢāļāļąāļāļāļēāļĢāļāđāļāļāļīāļāļāļĨāļēāļāļāļĒāđāļēāļāđāļŦāļĄāļēāļ°āļŠāļĄāđāļāđāļāđāļĨāļ°āļāļąāđāļāļāļāļ
āļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄ APNs: āļĢāļēāļāļāļēāļāļāļāļāļāļēāļĢāđāļāđāļāđāļāļ·āļāļāļāļ iOS
Apple Push Notification service (APNs) āļāļ·āļāļāļĢāļīāļāļēāļĢāļāļĨāļēāļāļāļĩāđāļāļąāļāļāļēāļĢāļāļēāļĢāļŠāđāļ push notification āđāļāļĒāļąāļāļāļļāļāļāļĢāļāđāļāļāļ Apple āļāļēāļĢāđāļāđāļēāđāļāļŠāļāļēāļāļąāļāļĒāļāļĢāļĢāļĄāļāļāļāļĄāļąāļāļāļģāđāļāđāļāļāđāļāļāļēāļĢāļāļāļāļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđāđāļāđāļāļĒāđāļēāļāļĄāļĩāļāļĢāļ°āļŠāļīāļāļāļīāļ āļēāļ
APNs āļāļģāļāļēāļāļāļĒāđāļēāļāđāļĢ?
āļāļēāļĢāļŠāļ·āđāļāļŠāļēāļĢāđāļāļĩāđāļĒāļ§āļāđāļāļāļāļąāļāļŠāļēāļĄāļāļđāđāđāļĨāđāļāļŦāļĨāļąāļ: āđāļāļ iOS, APNs āđāļĨāļ°āđāļāļīāļĢāđāļāđāļ§āļāļĢāđ backend āļāļąāđāļāļāļāļāļāļąāđāļāļŦāļĄāļāđāļāđāļāļāļąāļāļāļĩāđ:
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 āļāļļāļāļāļĢāļąāđāļāļāļĩāđāđāļāļīāļāđāļāļ
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 āļĄāļĩāļŠāļāļāļŠāļ āļēāļāđāļ§āļāļĨāđāļāļĄāđāļĒāļāļāļąāļāđāļāļĒāļĄāļĩ endpoint āļāđāļēāļāļāļąāļ āđāļāđāļāļāļāļĩāđāļŠāļĢāđāļēāļāđāļāļŠāļ āļēāļāđāļ§āļāļĨāđāļāļĄāļŦāļāļķāđāļāļāļ°āđāļāđāđāļāļāļĩāļāļŠāļ āļēāļāđāļ§āļāļĨāđāļāļĄāđāļĄāđāđāļāđ
| āļŠāļ āļēāļāđāļ§āļāļĨāđāļāļĄ | Endpoint | āļāļēāļĢāđāļāđāļāļēāļ | |--------------|----------|-----------| | Sandbox | api.sandbox.push.apple.com | Debug, TestFlight | | Production | api.push.apple.com | App Store |
āļāļģāļāļēāļĄāļāļĩāđ 2: āļāļąāļāļāļēāļĢāļāļąāļāđāļāđāļāļāļŦāļĄāļāļāļēāļĒāļļāļāļĒāđāļēāļāđāļĢ?
Device token āļāļēāļāđāļāļĨāļĩāđāļĒāļāđāļāđāļāđāļ§āļĒāļŦāļĨāļēāļĒāđāļŦāļāļļāļāļĨ: āļāļēāļĢāļāļ·āļāļāđāļēāļĢāļ°āļāļ āļāļēāļĢāļāļīāļāļāļąāđāļāļāļāļāļļāļāļāļĢāļāđāđāļŦāļĄāđ āļŦāļĢāļ·āļāļāļēāļĢāļĢāļĩāđāļāļĢāļāđāļāđāļāļĢāļ°āļĒāļ°āđāļāļĒ APNs āđāļāļīāļĢāđāļāđāļ§āļāļĢāđāļāđāļāļāļāļąāļāļāļēāļĢāļāļąāļāļāļēāļĢāļāļāļāļāļĨāļąāļāļāđāļāļāļīāļāļāļĨāļēāļāļāļāļ APNs āļāļĒāđāļēāļāđāļŦāļĄāļēāļ°āļŠāļĄ
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 āļāđāļ§āļĒāļāļĨāļļāļāđāļāļāđāļŦāđāļāļģāļāļēāļāļāļ·āđāļāļŦāļĨāļąāļāđāļāļ·āđāļāļāļģāđāļāļīāļāļāļēāļāđāļāļĒāđāļĄāđāđāļŠāļāļāļāļēāļĢāđāļāđāļāđāļāļ·āļāļāđāļ āđ āļāđāļāļāļđāđāđāļāđ
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
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 āđāļāđāļāļŦāļąāļ§āļāđāļāļāļĩāđāļāļāļāđāļģāđāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ āļāļđāđāļŠāļĄāļąāļāļĢāļāļ§āļĢāļĢāļđāđāļāļąāļāđāļāļĢāļ·āđāļāļāļĄāļ·āļāđāļĨāļ°āđāļāļāļāļīāļāđāļāļāļēāļĢāļ§āļīāļāļīāļāļāļąāļĒ
āļāļĢāļ§āļāļŠāļāļāļŠāļāļēāļāļ°āļāļēāļĢāļĨāļāļāļ°āđāļāļĩāļĒāļ
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 āđāļāđāđāļāļĒāļāļĢāļ:
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
āļāļēāļĢāļāļąāļāļāļēāļĢāļāđāļāļāļīāļāļāļĨāļēāļāļāļāļāđāļāļĢāļ·āļāļāđāļēāļĒ
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 āļāđāļ§āļĒāđāļŦāđāļāļēāļĢāļāļąāļāļāļēāļĢāđāļāđāļāļāļāļĨāļāļāļ āļąāļĒāļāđāļāļāļēāļĢāļāļģāļāļēāļāļāļĢāđāļāļĄāļāļąāļāđāļāļŠāļ āļēāļāđāļ§āļāļĨāđāļāļĄāđāļāļāļāļāļēāļ
āļāļēāļĢāđāļāđāļāđāļāđāļāļāđāļāļāđāļĨāļāļąāļĨ
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 āļāļēāļĢāđāļāđāļāđāļāđāļāļāđāļāļāđāļĨāļāļąāļĨ āļāļēāļĢāļāļąāļāļāļēāļĢāļāđāļāļāļīāļāļāļĨāļēāļ
āđāļĢāļīāđāļĄāļāļķāļāļāđāļāļĄāđāļĨāļĒ!
āļāļāļŠāļāļāļāļ§āļēāļĄāļĢāļđāđāļāļāļāļāļļāļāļāđāļ§āļĒāļāļąāļ§āļāļģāļĨāļāļāļŠāļąāļĄāļ āļēāļĐāļāđāđāļĨāļ°āđāļāļāļāļāļŠāļāļāđāļāļāļāļīāļāļāļĢāļąāļ
āđāļāđāļ
āđāļāļĢāđ
āļāļāļāļ§āļēāļĄāļāļĩāđāđāļāļĩāđāļĒāļ§āļāđāļāļ

āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ StoreKit 2: āļāļēāļĢāļāļąāļāļāļēāļĢāļāļēāļĢāļŠāļĄāļąāļāļĢāļŠāļĄāļēāļāļīāļāđāļĨāļ°āļāļēāļĢāļāļĢāļ§āļāļŠāļāļāđāļāđāļŠāļĢāđāļ
āđāļāļĩāđāļĒāļ§āļāļēāļāļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ iOS āđāļāļĩāđāļĒāļ§āļāļąāļ StoreKit 2 āļāļēāļĢāļāļąāļāļāļēāļĢāļāļēāļĢāļŠāļĄāļąāļāļĢāļŠāļĄāļēāļāļīāļ āļāļēāļĢāļāļĢāļ§āļāļŠāļāļāđāļāđāļŠāļĢāđāļ āđāļĨāļ°āļāļēāļĢāļāļģāļāļēāļĢāļāļ·āđāļāđāļāđāļāļāđāļāđāļāđ āļāļĢāđāļāļĄāļāļąāļ§āļāļĒāđāļēāļāđāļāđāļ Swift āļāļĩāđāđāļāđāļāļēāļāđāļāđāļāļĢāļīāļ

Swift Testing Framework āļŠāļąāļĄāļ āļēāļĐāļāđ 2026: āļĄāļēāđāļāļĢ #expect āđāļĨāļ° #require āđāļāļĩāļĒāļāļāļąāļ XCTest
āđāļĢāļĩāļĒāļāļĢāļđāđ Swift Testing Framework āđāļŦāļĄāđāļŠāļģāļŦāļĢāļąāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ iOS: āļĄāļēāđāļāļĢ #expect āđāļĨāļ° #require āļāļēāļĢāļĒāđāļēāļĒāļāļēāļ XCTest āđāļāļāđāļāļīāļĢāđāļāļāļąāđāļāļŠāļđāļāđāļĨāļ°āļāđāļāļāļīāļāļāļĨāļēāļāļāļĩāđāļāļāļāđāļāļĒ

25 āļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ Swift āļĒāļāļāļāļīāļĒāļĄāļŠāļģāļŦāļĢāļąāļāļāļąāļāļāļąāļāļāļē iOS
āđāļāļĢāļĩāļĒāļĄāļāļąāļ§āļŠāļģāļŦāļĢāļąāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ iOS āļāđāļ§āļĒ 25 āļāļģāļāļēāļĄ Swift āļāļĩāđāļāļāļāđāļāļĒāļāļĩāđāļŠāļļāļ: optionals, closures, ARC, protocols, async/await āđāļĨāļ° pattern āļāļąāđāļāļŠāļđāļ