Notification Payload — 是服务器通过APNS发送到iOS设备的JSON结构,定义推送通知的内容及其接收时的行为。负载包含必需和可选的键,用于控制文本、声音、badge、媒体附件和后台处理。根据Apple Developer Documentation, 2026,负载的最大大小对于普通通知为4096字节,对于VoIP推送为5120字节,这对传输的数据量施加了严格的限制。
要点
Notification Payload(通知负载)— 是服务器发送到APNS(Apple推送通知服务)以传递到iOS设备的JSON对象。负载包含系统显示通知所需的所有数据:标题、文本、声音、badge和用于后台处理的元数据。负载结构由Apple严格规定,并包含系统正确处理所需的必需键。
当服务器通过HTTP/2 API APNS发送推送通知时,请求包含授权标头和JSON正文(负载)。APNS检查负载的有效性:如果JSON不正确或超出大小限制,Apple服务器将返回400 Bad Request错误。验证后,APNS将负载传递到设备,iOS系统解析负载并确定如何处理通知 — 显示横幅、启动后台任务或播放声音。
APNS负载格式从iOS 2中的简单文本负载演变为现代版本中的多组件JSON结构。iOS 10通过mutable-content带来了对媒体附件的支持,iOS 12通过thread-id添加了通知分组,iOS 15引入了supports-live-activities以支持Live Activities。如今,根据通知所需的行为,负载最多可以包含15个不同的键。
负载的根对象包含aps字典和顶层的可选自定义字段。aps字典是唯一的必需元素,但在其内部,根据通知类型可以出现不同的键组合:alert、badge、sound、content-available、mutable-content、interruption-level等。
| aps键 | 类型 | 用途 |
|---|---|---|
| alert | String或Dictionary | 通知文本或包含title、subtitle、body、本地化的对象 |
| badge | Number | 应用程序图标上的数字;0删除badge |
| sound | String | 音频文件名或default表示系统声音 |
| content-available | Number (1) | 后台激活标志;1 = silent push |
| mutable-content | Number (1) | 激活Service Extension以修改内容的标志 |
| category | String | 用于按钮和Content Extension的类别标识符 |
| thread-id | String | 用于通知分组的组标识符 |
| interruption-level | String | 中断级别:passive、active、time-sensitive、critical |
| relevance-score | Number (0–1) | 通知对智能排名系统的优先级 |
alert键可以是简单字符串(成为通知正文)或包含title、subtitle、body字段的字典。字典格式允许将标题和副标题与主文本分开设置。对于本地化通知,使用title-loc-key、title-loc-args、loc-key、loc-args键,它们引用应用程序的Localizable.strings。这允许发送没有特定语言文本的负载 — 应用程序会自动替换翻译。
从iOS 15开始,Apple添加了Focus Mode机制,要求开发者指定通知的中断级别。interruption-level接受以下值:passive(无声音、不唤醒屏幕)、active(标准行为)、time-sensitive(穿透焦点,需要特殊授权)和critical(医疗/紧急情况)。relevance-score(0–1)键帮助Focus系统对同一类别内的通知进行排序。
thread-id键将通知分组到Notification Center中的组。所有具有相同thread-id的通知显示为一个组,用户可以展开。这对于消息应用尤其有用,其中来自同一联系人的消息被分组在一起,或者对于发送许多相同类型通知的应用。
自定义字段 — 是开发人员为向设备传输附加数据而添加的aps字典之外的任何键。服务器将它们包含在负载的根JSON对象中,应用程序通过UNNotificationContent中的userInfo接收它们。自定义字段不应重复aps中的键名,以避免解析时的冲突。
主要限制 — 负载的总大小不应超过4096字节。自定义字段与必需的aps键竞争此限制,因此最小化传输数据的大小很重要。使用短的键名(例如,用uid代替user-id),避免大型JSON结构,并仅传输标识符,而不是完整的数据对象。
自定义字段来自服务器,未经检查不应信任。始终验证解析时自定义字段的类型和值:通过optional binding检查键是否存在,使用as? String/Int/Dictionary转换为期望的类型,并处理值缺失的情况。切勿对来自负载的数据使用force unwrap (!) — 服务器可能发送不正确的数据,应用程序将崩溃。
{
"aps": {
"alert": {
"title": "新消息",
"body": "你好!最近怎么样?"
},
"badge": 5,
"sound": "default",
"category": "message",
"thread-id": "chat_4521",
"mutable-content": 1
},
"sender-id": "user_789",
"chat-id": "chat_4521",
"message-type": "text",
"image-url": "https://cdn.example.com/img.jpg"
}
在项目的所有负载中使用统一的命名风格。kebab-case(message-type)或camelCase(messageType)— 两种方法都可以接受,但重要的是在项目范围内遵循一种。避免长名称:用uid代替user-identifier,用img代替profile-image-url。键名中的每个字符都是4096限制中的一个字节。
不同的场景推送通知需要负载中不同的键组合。让我们看几个典型示例:简单文本通知、本地化通知、Silent Push和带媒体附件的Rich Notification。
带文本和声音的基本负载 — 向用户显示通知的最小配置。作为字符串的alert提供简短消息,sound default播放标准系统声音。Badge是可选的,在图标上设置计数器。category和thread-id用于分组和交互性。
{
"aps": {
"alert": "提醒:会议在15分钟后",
"badge": 3,
"sound": "default"
}
}
要发送到不同语言的设备,使用本地化键而不是固定文本。title-loc-key引用应用程序Localizable.strings中的键,title-loc-args替换参数。这允许向所有设备发送一个负载,应用程序以相应语言显示文本。
{
"aps": {
"alert": {
"title-loc-key": "NEW_MESSAGE_TITLE",
"title-loc-args": ["安娜"],
"loc-key": "NEW_MESSAGE_BODY",
"loc-args": ["你好!"]
},
"sound": "message.caf"
}
}
对于不显示通知的后台同步,使用content-available: 1和没有alert。自定义字段指示操作类型和要处理的数据。系统在后台激活应用程序,调用带fetchCompletionHandler的didReceiveRemoteNotification,应用程序执行同步。
{
"aps": {
"content-available": 1
},
"sync-type": "invalidate-cache",
"timestamp": "2026-07-03T12:00:00Z"
}
要显示媒体附件,需要mutable-content: 1来激活Service Extension和自定义字段中的图片URL。mutable-content: 1向系统发出信号启动UNNotificationServiceExtension,它将从URL下载图片并作为UNNotificationAttachment添加。category指向注册的类别以显示操作按钮。
{
"aps": {
"alert": {
"title": "新产品",
"body": "查看新系列"
},
"category": "product",
"mutable-content": 1
},
"media-url": "https://cdn.example.com/product.jpg"
}
UNNotificationContent.userInfo包含系统处理后收到的负载的完整字典。应用程序在UNUserNotificationCenter委托中收到通知时(在前台)、点击通知时以及在Service Extension和Content Extension中访问负载。正确的解析对于提取自定义数据和确定后续操作是必需的。
当用户点击通知时,系统调用UNUserNotificationCenterDelegate中的didReceive response方法。在response.notification.request.content.userInfo中包含完整负载。开发者提取自定义字段,确定操作类型(例如,打开聊天、转到产品),并在应用程序中调用相应的导航。
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let userInfo = response.notification
.request.content.userInfo
guard let chatId = userInfo["chat-id"] as? String
else {
completionHandler()
return
}
let messageType = userInfo["message-type"]
as? String ?? "text"
NavigationRouter.shared.navigate(
to: .chat(chatId: chatId,
messageType: messageType))
completionHandler()
}
Service Extension在通知显示前接收负载并可以修改它。负载验证 — didReceive中的第一步:检查必需的自定义字段是否存在、附件URL的正确性和数据类型。如果负载无效,立即使用原始内容调用completion handler,不要浪费时间进行无用的处理。
为了在生产中调试推送通知,使用负载的结构化日志记录。OSLog允许以类别notifications和debug级别记录负载。在服务器端,跟踪APNS响应:成功响应包含apns-id以匹配发送的负载,错误400指示无效JSON或超出大小。
常见问题
负载的最大大小 — 普通推送通知为4096字节,VoIP推送(PushKit)为5120字节。超出时,APNS返回400 Bad Request错误。大小以字节计算,而不是字符 — 请考虑UTF-8编码。
在alert中使用loc-key、title-loc-key、loc-args和title-loc-args键。应用程序根据设备语言从其自己的Localizable.strings中替换翻译。这允许向所有设备发送一个负载,无论其语言如何。
content-available在后台激活应用程序进行数据处理(silent push),不显示通知。mutable-content激活Service Extension以在显示前修改内容。两个键可以一起使用,用于后台处理和后续的通知修改。
使用APNS Sandbox进行测试,并检查Apple服务器的HTTP响应:200 OK表示成功发送。对于结构验证,在CI/CD pipeline中使用JSON模式。在Xcode中,通过模拟器使用命令xcrun simctl push发送测试通知。
apns-id — APNS系统中推送通知的唯一标识符,在成功发送的响应中返回。用于通过Logs API跟踪传递和调试。服务器应为每个发送的通知保存apns-id。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。