Интеграция push-уведомлений через Apple Push Notification Service (APNs)
Push-уведомления в iOS выглядят как несложная задача ровно до момента, когда уведомления доходят на симулятор, но не на реальное устройство. Или приходят в дев-окружении и пропадают в продакшене. Причина почти всегда одна: неправильная конфигурация сертификатов или несоответствие окружения (sandbox vs production). APNs — строгая система, и любое несоответствие конфигурации приводит к молчаливой потере уведомлений без ошибки на клиенте.
Настройка: p8 ключ vs p12 сертификат
Apple поддерживает два способа аутентификации для APNs:
APNs Auth Key (.p8) — JWT-токен. Один ключ на весь аккаунт, не истекает (только если не отозвать), работает для sandbox и production без переключения. Это рекомендуемый способ с 2016 года.
APNs Certificate (.p12) — SSL-сертификат. Истекает через год, отдельный для sandbox и production, привязан к конкретному Bundle ID. Устаревший способ, но ещё встречается в legacy проектах.
Для нового проекта всегда выбираем .p8 через Apple Developer Console → Certificates, Identifiers & Profiles → Keys.
Конфигурация на стороне iOS-приложения
// AppDelegate.swift
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { granted, error in
guard granted else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
return true
}
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
// Отправляем token на сервер
NotificationService.shared.registerToken(tokenString)
}
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("APNs registration failed: \(error)")
}
didFailToRegisterForRemoteNotificationsWithError — этот метод многие забывают реализовать. Без него молчаливый сбой регистрации никак не логируется.
Capabilities и Entitlements
В Xcode: Target → Signing & Capabilities → + Capability → Push Notifications. Это добавляет aps-environment в .entitlements. Значение — development для debug, production для release. Несоответствие этого значения при отправке уведомления — самая частая причина BadDeviceToken ошибки от APNs.
Также добавляем Background Modes → Remote notifications, если нужна фоновая обработка.
Типы payload
Стандартный alert:
{
"aps": {
"alert": {
"title": "Новое сообщение",
"body": "Иван написал вам"
},
"badge": 3,
"sound": "default"
},
"userId": "u123",
"messageId": "m456"
}
Silent push (фоновое обновление без UI):
{
"aps": {
"content-available": 1
},
"syncType": "messages"
}
Silent push требует Background Modes → Remote notifications в Capabilities. На iOS 13+ Apple ограничивает количество silent push до ~3 в час — нельзя использовать как замену polling.
Notification Service Extension
Для модификации уведомлений перед показом (расшифровка, загрузка изображения) — NotificationServiceExtension. Отдельный таргет в Xcode, обрабатывает уведомления с mutable-content: 1 в payload:
class NotificationService: UNNotificationServiceExtension {
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
guard let bestAttempt = request.content.mutableCopy() as? UNMutableNotificationContent,
let attachmentURL = request.content.userInfo["imageUrl"] as? String,
let url = URL(string: attachmentURL) else {
contentHandler(request.content)
return
}
// Загружаем изображение и прикрепляем
downloadAttachment(from: url) { attachment in
if let attachment { bestAttempt.attachments = [attachment] }
contentHandler(bestAttempt)
}
}
}
Таймаут Extension — 30 секунд. Если не успели — APNs показывает оригинальное уведомление.
Токен и его жизненный цикл
Device token меняется: при переустановке приложения, при восстановлении из бэкапа, иногда при обновлении iOS. Сервер должен обновлять токен при каждом didRegisterForRemoteNotificationsWithDeviceToken. APNs возвращает 410 Gone при отправке на устаревший токен — сервер должен его удалять.
Отладка
-
Simulator: APNs работает только на физических устройствах (до Xcode 11.4 вообще не работал, с 11.4+ — через
.apnsфайл в симуляторе) -
Console.app: фильтр по
dasdиapsdпроцессам — там логи APNs daemon - Instruments → Push Notifications: трекинг delivery
Что входит в работу
- Настройка APNs Auth Key (.p8) в Apple Developer Console
- Capabilities и Entitlements в Xcode
- Регистрация, получение токена, обработка lifecycle токена
- Обработка foreground / background / terminated состояний
- Notification Service Extension для rich notifications
- Silent push для фоновой синхронизации
- Интеграция с backend для хранения и отправки токенов
Сроки
Базовая интеграция APNs с alert-уведомлениями: 1 день. С rich notifications, silent push, Extension и полным lifecycle токена: 2–3 дня.







