Notification Service Extension — это расширение iOS, которое перехватывает push-уведомление сразу после получения, но до отображения пользователю. Расширение может расшифровать зашифрованный payload, загрузить и прикрепить медиафайл, изменить текст или заголовок уведомления в реальном времени. По данным Apple Developer Documentation (2025), для активации расширения сервер должен отправить ключ mutable-content:1 в атрибутах уведомления — это единственное условие запуска UNNotificationServiceExtension.
Главное
Notification Service Extension — это app extension в iOS, которое перехватывает входящее push-уведомление на стороне устройства и позволяет изменить его содержимое до того, как пользователь его увидит. Это единственный тип расширения уведомлений, который работает с контентом, а не с отображением.
Ключевое отличие от Notification Content Extension: Service Extension работает до показа уведомления и может изменить заголовок, тело, саундфайл и вложения. Content Extension работает после показа и управляет только визуальным представлением готового уведомления. Эти два расширения могут работать вместе: Service Extension загружает изображение, а Content Extension отображает его в кастомном интерфейсе.
Расширение активируется автоматически при получении push-уведомления с атрибутом mutable-content:1 в aps-словаре. iOS запускает расширение в фоновом режиме, передаёт ему оригинальный UNNotificationRequest и ожидает модифицированную версию для отображения.
UNNotificationServiceExtension получает полный UNNotificationRequest с оригинальным содержимым. Расширение может модифицировать любые поля UNNotificationContent: title, subtitle, body, userInfo, attachments и sound. Изменения применяются до того, как уведомление будет показано.
Расшифровка контента — если push-уведомление содержит зашифрованный payload, расширение расшифровывает его до показа. Загрузка медиа — прикрепление изображения или видео к уведомлению. Локализация — адаптация текста уведомления под региональные настройки устройства. Обогащение данных — добавление дополнительной информации из локального хранилища или кэша.
По данным Apple, наиболее частый сценарий среди приложений — загрузка изображений для богатых медиа-уведомлений. Сервер отправляет URL изображения в payload, расширение скачивает его во временную директорию и создаёт UNNotificationAttachment, который система отображает в стандартном или кастомном интерфейсе.
Расширение может полностью переписать text уведомления, заменить заголовок или добавить subtitle. Например, приложение мессенджера может получить зашифрованное уведомление, расшифровать его в расширении и отобразить читаемый текст. Или новостное приложение может добавить категорию новости в сабтайтл перед показом.
override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
let content = request.content.mutableCopy()
as! UNMutableNotificationContent
if let imageURL = content.userInfo["media-url"]
as? String {
downloadAndAttach(imageURL: imageURL,
content: content,
handler: contentHandler)
}
}
UNNotificationServiceExtension — базовый класс, от которого наследуется Service Extension. Класс определяет два метода жизненного цикла: didReceive(_:withContentHandler:) — основной метод обработки, и serviceExtensionTimeWillExpire() — обработчик истечения времени.
didReceive(_:withContentHandler:) вызывается при получении уведомления. Расширение получает UNNotificationRequest и замыкание contentHandler, которое нужно вызвать с модифицированным UNMutableNotificationContent. Расширение обязано вызвать contentHandler — если этого не сделать, iOS покажет оригинальное уведомление после истечения таймаута.
Важно: расширение может обрабатывать только одно уведомление за раз. Если приходят несколько уведомлений одновременно, iOS создаёт отдельные экземпляры расширения для каждого. Нельзя использовать глобальное состояние для последовательной обработки.
serviceExtensionTimeWillExpire() вызывается системой, когда оставшееся время выполнения подходит к концу. В этом методе нужно немедленно вызвать contentHandler с тем контентом, который готов на данный момент — даже если медиафайл ещё не загрузился. Если не вызвать contentHandler в этом методе, iOS покажет оригинальное уведомление.
Рекомендуется сохранять в этом методе минимально приемлемый контент — например, уведомление с текстом и заголовком, но без изображения, загрузка которого не завершилась вовремя.
UNNotificationAttachment — объект, создаваемый расширением для прикрепления медиафайла к уведомлению. Расширение загружает файл из сети, сохраняет его во временную директорию и создаёт UNNotificationAttachment с указанием типа контента.
UNNotificationAttachment создаётся с помощью инициализатора init(identifier:url:options:). URL должен указывать на локальный файл во временной директории, доступной для расширения. После создания attachment добавляется в массив attachments UNMutableNotificationContent.
Apple рекомендует использовать URLSession с фоновой конфигурацией для загрузки — при использовании стандартной URLSession загрузка блокирует поток и расходует время из 30-секундного лимита. Фоновая URLSession продолжает загрузку даже при завершении расширения, а результат может быть использован при следующем запуске.
Если сервер отправляет зашифрованное уведомление, расширение должно расшифровать payload перед вызовом contentHandler. Расшифровка обычно включает запрос ключа из Keychain или App Group, дешифрование через CommonCrypto и замену body или userInfo уведомления. При ошибке расшифровки следует вызвать contentHandler с оригинальным содержимым — чтобы пользователь хотя бы видел, что уведомление пришло, пусть и нечитаемое.
func downloadAndAttach(
imageURL: String,
content: UNMutableNotificationContent,
handler: @escaping (UNNotificationContent) -> Void
) {
let task = URLSession.shared.dataTask(with:
URL(string: imageURL)!) { data, _, _ in
let url = FileManager.default
.temporaryDirectory
.appendingPathComponent("image.jpg")
try? data?.write(to: url)
let attachment = try? UNNotificationAttachment(
identifier: "image", url: url)
content.attachments = [attachment].compactMap { $0 }
handler(content)
}
task.resume()
}
Notification Service Extension работает в жёстких временных рамках. iOS выделяет фиксированное время на выполнение — примерно 30 секунд с момента активации. Если расширение не вызвало contentHandler за это время, система принудительно завершает процесс и показывает оригинальное уведомление без изменений.
Рекомендуется реализовать многоуровневый fallback: сначала попытаться загрузить медиа, при успехе — вызвать contentHandler с полным контентом; при неудаче — вызвать contentHandler с текстом, но без медиа; при критической ошибке — передать оригинальное содержимое. Такой подход гарантирует, что пользователь всегда увидит уведомление, а не пустой экран.
По данным Apple, наиболее частой причиной таймаутов является загрузка больших медиафайлов при медленном соединении. Для снижения риска рекомендуется оптимизировать размер изображений на сервере — отправлять превью до 300 КБ вместо полного разрешения. Полноразмерные изображения стоит загружать уже при открытии приложения.
Для отслеживания таймаутов и ошибок расширения можно использовать os_log для записи диагностических сообщений в Unified Logging System. Хотя прямое логирование в файл в расширении затруднено, os_log позволяет анализировать производительность через Console.app на устройстве разработчика. Apple рекомендует добавлять метрики на каждый вызов didReceive — время загрузки, размер файла, результат операции.
override func serviceExtensionTimeWillExpire() {
let fallback = bestEffortContent as?
UNMutableNotificationContent
?? request.content.mutableCopy()
as! UNMutableNotificationContent
contentHandler(fallback)
}
Часто задаваемые вопросы
Сервер добавляет ключ mutable-content:1 в aps-словарь push-уведомления. Без этого параметра система игнорирует расширение и показывает стандартное уведомление.
Нет. mutable-content:1 — обязательное условие активации Service Extension. Если ключ отсутствует или установлен в 0, уведомление показывается без вызова расширения.
iOS принудительно завершает расширение и показывает оригинальное уведомление без изменений. Чтобы избежать этого, реализуйте serviceExtensionTimeWillExpire() с минимально приемлемым контентом.
Через App Group (общий UserDefaults или файл) или Keychain с общим доступом между приложением и расширением. Прямая передача ключей в payload уведомления небезопасна.
До 4 вложений на одно уведомление, каждое до 50 МБ. Суммарный размер вложений влияет на время загрузки — чем больше файлов, тем выше риск таймаута.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также