Notification Extension 是 iOS 的一种机制,允许在向用户显示推送通知之前修改其内容和外观。扩展在操作系统端以独立进程运行:UNNotificationServiceExtension 处理传入内容,而 UNNotificationContentExtension 管理界面。根据 Apple Developer Documentation, 2026,Service Extension 在通知显示前有最多 30 秒来执行任务。这个限制在加载附件或解密数据时至关重要。
要点
Notification Extension 是 iOS 的一个软件组件,它扩展了推送通知的标准行为,增加了修改内容和自定义界面的能力。与系统以标准形式显示的普通通知不同,扩展允许开发者在显示前影响内容,并创建独特的用户界面。
Apple 为不同目的提供两种 Notification Extension:UNNotificationServiceExtension 负责处理传入的 payload,而 UNNotificationContentExtension 管理显示。Service Extension 在通知显示前启动,根据 Apple Developer Documentation 有有限的执行时间 — 最多 30 秒。Content Extension 在用户与通知交互后激活,并显示自定义视图。
标准 iOS 推送通知由系统根据 JSON-payload 中的 alert、title 和 subtitle 字段自动显示。Notification Extension 接管控制权:Service Extension 接收原始 payload,对其进行修改,然后传递给系统显示。Content Extension 用带有任意控制元素的自定义界面替换标准横幅。
当设备收到带有 mutable-content: 1 键的推送通知时,系统在独立进程中启动 Service Extension。扩展进程与主应用程序隔离,并拥有自己的沙盒,内存限制为 50 MB。处理完成后,扩展调用 completion handler,将修改后的 UNNotificationContent 传递给系统显示。
UNNotificationServiceExtension 是在推送通知显示给用户之前以编程方式修改它们的主要工具。当收到 payload 中设置了 mutable-content: 1 标志的通知时,扩展会自动激活。在 30 秒内,扩展可以加载媒体附件、修改文本、解密加密数据或丰富内容。
扩展实现了 UNNotificationServiceExtension 协议中的两个关键方法。didReceive 方法接收带有原始 UNNotificationRequest 的传入请求,并允许通过带有新 UNNotificationContent 的 completion handler 进行修改。serviceExtensionTimeWillExpire 方法在超时前 1 秒由系统调用 — 在此方法中需要完成处理并传递当前(可能部分)结果。
考虑服务器在自定义字段中发送带有图像 URL 的推送的场景。Service Extension 通过网络加载此图像,创建 UNNotificationAttachment 并将其添加到内容中。UNNotificationAttachment 接受本地图像、视频或音频文件,并自动将其复制到扩展的沙盒中。创建附件后,扩展将更新后的内容传递给系统。
class NotificationService: UNNotificationServiceExtension {
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler:
@escaping (UNNotificationContent) -> Void) {
let userInfo = request.content.userInfo
guard let imageURL = URL(string: userInfo["image-url"] as! String)
else { contentHandler(request.content); return }
let attachment = try! UNNotificationAttachment(
identifier: "image", url: imageURL,
options: [UNNotificationAttachmentOptionsTypeHintKey: "jpg"])
let modifiedContent = request.content.mutableCopy()
as! UNMutableNotificationContent
modifiedContent.attachments = [attachment]
contentHandler(modifiedContent)
}
override func serviceExtensionTimeWillExpire() {
contentHandler?(bestAttemptContent ?? request.content)
}
}
在频繁发送带有相同附件的通知时,建议在设备上缓存下载的文件。FileManager 提供对扩展缓存目录的访问,该目录在启动之间保留。这减少了后续通知的处理时间并降低了网络负载。据 Apple 称,缓存附件可以将处理时间减少到 2-5 秒,而不是完全加载。
UNNotificationContentExtension 允许用 Interface Builder 或 SwiftUI 中创建的自定义界面替换标准通知横幅。当用户对通知执行操作时扩展被激活:触摸、向下滑动或 3D Touch。Content Extension 接收已经由 Service Extension 处理的内容,并在自定义视图中显示。
每个 Content Extension 通过 Info.plist 绑定到一个或多个通知类别。类别由服务器在 APNS payload 中用 category 字段确定。系统根据接收到的通知类别自动选择正确的扩展。界面通过 storyboard 使用标准 UIKit 组件或 SwiftUI View 构建。
Content Extension 支持在 UNNotificationAction 中定义的自定义按钮和触摸处理。UNNotificationAction 在类别注册阶段创建,可以通过 UNTextInputNotificationAction 进行文本输入。当用户按下按钮时,扩展收到带有操作标识符的 didReceive 回调,并可以执行相应的逻辑 — 打开 URL、向服务器发送请求或更新界面。
Content Extension 在隔离进程中运行,拥有自己的执行循环和约 50 MB 的内存限制。扩展的性能至关重要,因为系统 watchdog 会在超出限制时终止进程。建议避免在 Content Extension 内进行繁重计算、加载大图像和长时间网络请求。
UNNotificationAttachment 是向推送通知添加媒体文件的对象:图像、视频、音频或 GIF。附件从必须位于扩展沙盒中的本地文件 URL 创建。附件的最大大小不得超过 10 MB,否则系统将在创建时拒绝附件。
Apple 支持有限的一组媒体附件格式。图像 — JPEG、PNG、GIF(包括动画)、TIFF。视频 — MPEG、MP4、MOV,最大时长 30 秒。音频 — MP3、AAC、WAV、CAF。对于每种格式,可以通过 UNNotificationAttachmentOptionsTypeHintKey 指定类型,这有助于系统正确处理文件。
由于推送 payload 只包含 URL 而不是文件本身,附件的加载必须在 Service Extension 内部完成。最大加载时间限制为 30 秒,因此建议使用带有最小设置的 URLSession,并在信号弱时禁用加载。如果附件未及时加载,通知将不显示媒体 — 这是系统的默认行为。
让我们看一个完整的 Notification Extension 示例,它加载图像、下载并将其添加到通知中。NotificationService 继承自 UNNotificationServiceExtension 并重写 didReceive 方法。示例展示了可选字段的处理、附件的创建以及使用修改后内容调用 completion handler。
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
self.bestAttemptContent =
(request.content.mutableCopy()
as! UNMutableNotificationContent)
guard let attachmentURLString =
bestAttemptContent?.userInfo["attachment-url"] as? String,
let url = URL(string: attachmentURLString)
else {
contentHandler(request.content)
return
}
URLSession.shared.downloadTask(with: url) {
[weak self] localURL, _, error in
guard let localURL = localURL, error == nil
else {
contentHandler(request.content)
return
}
let attachment = try! UNNotificationAttachment(
identifier: "media", url: localURL)
self?.bestAttemptContent?.attachments = [attachment]
contentHandler(self?.bestAttemptContent
?? request.content)
}.resume()
}
override func serviceExtensionTimeWillExpire() {
if let content = bestAttemptContent {
contentHandler?(content)
}
}
}
为了扩展的正确运行,需要在 Info.plist 中注册通知类别。NSExtensionPointIdentifier 键对于 Service Extension 设置为 com.apple.usernotifications.service,对于 Content Extension 设置为 com.apple.usernotifications.content。类别在 AppDelegate 中应用程序启动时通过 UNUserNotificationCenter 定义,扩展仅对 payload 中具有相应类别的通知激活。
常见问题
Service Extension 在通知显示前修改内容 — 添加媒体、更改文本、解密数据。Content Extension 在用户交互后用自定义界面替换通知界面。Service Extension 在显示前工作,Content Extension — 之后。
系统分配 30 秒用于在 didReceive 中执行代码。如果处理未在此时间内完成,将调用 serviceExtensionTimeWillExpire,需要在此传递当前结果。建议在考虑附件加载的情况下在 10-15 秒内完成。
是的,从 iOS 16 开始,SwiftUI 在 Content Extension 中得到支持。View 被包装在 UIHostingController 中并添加到 storyboard。然而,由于内存限制,建议仅对具有最少元素数量的简单界面使用 SwiftUI。
在加载错误或超过时间限制时,只需使用不带附件的原始内容调用 completion handler。系统将以标准形式显示通知,不带媒体。通过 OSLog 记录错误以进行诊断,但不要阻止通知的显示。
扩展在隔离进程中运行,限制约为 50 MB。超出限制时,系统通过 watchdog 终止进程。避免加载大文件、在内存中存储图像以及在使用 URLSession 时出现内存泄漏。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。