Notification Service Extension — это расширение iOS, которое перехватывает push-уведомление до его отображения пользователю и позволяет модифицировать контент. Расширение может расшифровать зашифрованные данные, скачать медиавложения, изменить текст или добавить кастомные поля. По данным Apple Developer, 2025, Notification Service Extension имеет до 30 секунд на выполнение задачи в фоновом режиме.
Главное
Notification Service Extension — это app extension в iOS, которое запускается перед отображением push-уведомления. Оно позволяет серверу отправлять минимальный payload, а расширение — дополнять его контентом: скачивать изображения, расшифровывать данные, заменять текст. Пользователь видит уже готовое, обработанное уведомление.
Когда iOS получает push-уведомление, система проверяет наличие расширения для данного приложения. Если расширение зарегистрировано, iOS запускает его в фоновом процессе и передаёт содержимое уведомления через метод didReceive(_:withContentHandler:). Расширение обрабатывает контент и вызывает contentHandler с модифицированным содержимым. Если расширение не успевает за 30 секунд, система отображает исходное уведомление.
Service Extension необходим в сценариях, где контент уведомления требует обработки на устройстве. Безопасные коммуникации: сервер отправляет зашифрованный payload, расширение расшифровывает его локально. Богатые медиауведомления: сервер отправляет URL изображения, расширение скачивает и прикрепляет его. Динамическая локализация: расширение подставляет текст на языке устройства.
| Сценарий | Без расширения | С расширением |
|---|---|---|
| Изображение | Не поддерживается | Скачивается и отображается |
| Шифрование | Сервер хранит ключ | Расшифровка на устройстве |
| Текст | Жёстко задан на сервере | Динамическая подстановка |
| Валидация | Не проверяется | Отмена вредоносных уведомлений |
Notification Service Extension работает в ограниченном окружении. Максимальное время выполнения — 30 секунд. Расширение не имеет доступа к основному хранилищу приложения (кроме App Group). Размер расширения ограничен 50 МБ. При превышении времени система вызывает contentHandler с исходным контентом, и все изменения теряются.
В Xcode Notification Service Extension добавляется через создание нового target типа Notification Service Extension. Xcode генерирует шаблон класса NotificationService, наследующий от UNNotificationServiceExtension, с двумя методами: didReceive и serviceExtensionTimeWillExpire.
В Xcode выберите File → New → Target → Notification Service Extension. Укажите имя (например, PushNotificationService) и язык Swift. Убедитесь, что target добавлен в основное приложение и имеет правильную подпись. После создания будет сгенерирован класс NotificationService.swift с базовой реализацией.
Info.plist расширения содержит ключ NSExtension с подключами NSExtensionPointIdentifier (com.apple.usernotifications.service) и NSExtensionPrincipalClass (имя вашего контроллера). Дополнительно можно указать NSExtensionAttributes с правилами активации расширения. Эти настройки Xcode генерирует автоматически.
Базовая реализация переопределяет didReceive, модифицирует контент уведомления и вызывает contentHandler. Если обработка занимает слишком много времени, вызывается serviceExtensionTimeWillExpire, где необходимо завершить работу с текущим состоянием.
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(
request: UNNotificationRequest,
withContentHandler handler: @escaping (UNNotificationContent) -> Void
) {
contentHandler = handler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
bestAttemptContent.title = "[Обработано] \(bestAttemptContent.title)"
contentHandler?(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
Одним из ключевых применений Service Extension является расшифровка содержимого уведомления на устройстве. Сервер отправляет зашифрованный payload, а расширение расшифровывает его, используя ключ, хранящийся в Keychain или App Group. Это гарантирует, что контент недоступен для перехвата при передаче.
Сервер шифрует тело уведомления с использованием симметричного ключа (AES-256). Ключ шифрования предварительно согласовывается между клиентом и сервером. Расширение получает зашифрованную строку в поле data payload, расшифровывает её и подставляет в поля уведомления. Пользовательский ключ может быть получен из Keychain после аутентификации.
override func didReceive(
request: UNNotificationRequest,
withContentHandler handler: @escaping (UNNotificationContent) -> Void
) {
guard let content = request.content.mutableCopy()
as? UNMutableNotificationContent else {
handler(request.content)
return
}
guard let encryptedData = content.userInfo["encrypted_data"]
as? String else {
handler(content)
return
}
let decrypted = CryptoService.decrypt(encryptedData)
content.body = decrypted.body
content.title = decrypted.title
handler(content)
}
Ключ для расшифровки не должен храниться в коде расширения или NSUserDefaults. Используйте iOS Keychain с доступом через App Group, чтобы и приложение, и расширение могли читать ключ. Для генерации ключа на клиенте используйте Security.framework с алгоритмом AES-256-GCM.
Самое частое применение Notification Service Extension — скачивание изображений, GIF и видео для последующего отображения в уведомлении. Сервер отправляет URL медиафайла, а расширение скачивает его, сохраняет во временную директорию и создаёт UNNotificationAttachment.
Расширение получает URL изображения из поля payload уведомления. Используя URLSession, расширение скачивает файл во временную директорию. После завершения загрузки создаётся UNNotificationAttachment с локальным URL. Вложение передаётся в модифицированный контент. iOS автоматически отображает изображение в стандартном интерфейсе или в Notification Content Extension.
private func downloadAndAttachMedia(
content: UNMutableNotificationContent,
mediaUrl: String,
handler: @escaping (UNNotificationContent) -> Void
) {
guard let url = URL(string: mediaUrl) else {
handler(content)
return
}
let task = URLSession.shared.downloadTask(with: url) { localUrl, _, error in
guard let localUrl = localUrl, error == nil else {
handler(content)
return
}
let attachment = try? UNNotificationAttachment(
identifier: "media",
url: localUrl,
options: nil
)
if let attachment = attachment {
content.attachments = [attachment]
}
handler(content)
}
task.resume()
}
iOS поддерживает следующие форматы для отображения в уведомлениях: JPEG, PNG, GIF (статичные), MPEG-4 видео до 50 МБ. Для аудиофайлов поддерживается MP3, AAC и ALAC. Важно: все медиафайлы должны быть скачаны в течение 30-секундного лимита. Для больших файлов рекомендуется использовать серверную обрезку или прогрессивную загрузку.
Управление 30-секундным лимитом — ключевая задача при разработке Notification Service Extension. Если расширение не успевает завершить обработку, система вызывает serviceExtensionTimeWillExpire и отображает исходный контент. Необходимо предусмотреть fallback-сценарии для каждого типа обработки.
Разделите задачи по приоритетам. Критичные модификации (расшифровка, базовая локализация) выполняйте в первую очередь. Опциональные улучшения (скачивание изображений, обогащение текста) — во вторую очередь. Используйте URLSession с таймаутами для сетевых запросов, чтобы не тратить весь лимит на одну операцию.
Если скачивание изображения не удалось или расшифровка payload вернула ошибку, расширение должно вызвать contentHandler с исходным контентом. Никогда не завершайте расширение без вызова contentHandler — это приводит к потере уведомления. Безопасный fallback всегда должен быть предусмотрен в коде расширения.
override func didReceive(
request: UNNotificationRequest,
withContentHandler handler: @escaping (UNNotificationContent) -> Void
) {
let content = (request.content.mutableCopy()
as? UNMutableNotificationContent) ?? request.content
// Критичная задача: расшифровка
var decryptedContent = tryDecryptPayload(content)
// Опциональная задача: медиа
guard let mediaUrl = decryptedContent.userInfo["media_url"]
as? String else {
handler(decryptedContent)
return
}
downloadAndAttachMedia(
content: decryptedContent,
mediaUrl: mediaUrl,
handler: handler
)
}
Для тестирования Notification Service Extension используйте Xcode: выберите target расширения, запустите на симуляторе и отправьте push-уведомление через терминал или Firebase Console. Логируйте каждый этап обработки через os_log — это помогает диагностировать проблемы с таймингами и ошибками загрузки.
Часто задаваемые вопросы
Система вызовет serviceExtensionTimeWillExpire, после чего отобразит исходное уведомление без изменений. Все скачанные файлы и модификации будут отброшены.
Да, если вызвать contentHandler с пустым контентом (UNNotificationContent с пустыми полями), уведомление не будет показано. Это используется для фильтрации спам-уведомлений или некорректных данных.
Токен можно передать в userInfo payload уведомления или получить из Keychain через App Group. Не рекомендуется хранить токены в UserDefaults расширения.
Расширение должно быть подписано тем же сертификатом разработчика, что и основное приложение. Для продакшена требуется production-сертификат с включённым capability Push Notifications.
Подключите устройство к Xcode, выберите target расширения в схеме запуска и отправьте push-уведомление через Firebase Console. Breakpoints в расширении работают так же, как в основном приложении.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также