iOS应用关闭(非后台)时接收推送自动递增角标方案咨询
嘿,这个问题我之前帮不少做聊天室类应用的开发者解决过——服务器逐个维护每个设备的角标计数确实太繁琐,还拉高了成本,给你几个更高效的方案,其中第一个完全匹配你的需求:
方案一:用Notification Service Extension实现自动增量角标(推荐)
这个方案的核心是利用iOS的通知扩展,在推送到达设备时(哪怕应用完全关闭)自动处理角标累加,服务器根本不需要维护每个设备的角标数,批量推送同一个payload就行。
实现步骤:
- 创建Notification Service Extension:在Xcode里给项目添加一个「Notification Service Extension」,这个扩展会在推送到达设备时被短暂唤醒,和主应用是否运行无关。
- 配置App Groups:因为主应用和扩展需要共享角标计数,所以要在两者的Capabilities里开启App Groups,设置同一个Group ID(比如
group.com.yourteam.yourapp)。 - 扩展里处理角标增量:在扩展的
didReceive方法中,读取共享存储的当前角标数,加上增量(每条推送加1),再更新通知的badge字段,最后保存新的计数。 - 主应用重置角标:当用户打开应用时,把角标清零,同时同步共享存储的计数。
代码示例:
Notification Service Extension代码:
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 bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) guard let content = bestAttemptContent else { contentHandler(request.content) return } // 从自定义字段获取增量,默认加1 let increment = content.userInfo["badge_increment"] as? Int ?? 1 // 从共享UserDefaults读取当前角标数 let sharedDefaults = UserDefaults(suiteName: "group.com.yourteam.yourapp")! var currentBadge = sharedDefaults.integer(forKey: "current_badge") currentBadge += increment // 更新通知角标 content.badge = NSNumber(value: currentBadge) // 保存更新后的计数 sharedDefaults.set(currentBadge, forKey: "current_badge") sharedDefaults.synchronize() contentHandler(content) } override func serviceExtensionTimeWillExpire() { // 超时 fallback,返回原始通知内容 if let handler = contentHandler, let content = bestAttemptContent { handler(content) } } }
主应用重置角标代码(AppDelegate或SceneDelegate):
import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 清零角标 UIApplication.shared.applicationIconBadgeNumber = 0 // 同步共享存储的计数为0 let sharedDefaults = UserDefaults(suiteName: "group.com.yourteam.yourapp")! sharedDefaults.set(0, forKey: "current_badge") sharedDefaults.synchronize() return true } }
服务器端推送Payload示例(批量推送所有设备用同一个):
{ "aps": { "alert": { "title": "新聊天消息", "body": "有人@了你" }, "mutable-content": 1, // 必须设置这个,才会触发通知扩展 "sound": "default" }, "badge_increment": 1 // 自定义字段,告诉扩展要加1 }
注意事项:
- 确保推送的
mutable-content字段设为1,否则扩展不会被触发。 - 应用卸载重装后,共享存储的计数会丢失,第一次推送会从0开始累加;如果需要准确对应服务器未读数,可以在主应用启动时请求服务器同步一次。
- 扩展的运行时间有限(约30秒),所以逻辑要尽量简单,避免复杂操作。
方案二:静默推送同步未读计数(适合需精准计数的场景)
如果你的角标需要严格对应服务器上的未读消息数(比如可能存在消息撤回、批量标记已读的情况),可以用这个方案:
- 服务器维护每个用户的未读消息数(而不是每个设备)。
- 有新消息时,给用户的所有设备发送静默推送(
content-available:1,无alert/sound),触发客户端(后台或刚关闭时,需用户开启后台刷新)请求服务器获取最新未读数,然后更新角标。 - 缺点:应用完全关闭且用户没开后台刷新时,静默推送不会触发,角标不会实时更新。
方案三:利用APNS批量推送的简化处理(仅限iOS 15+)
iOS 15及以后,APNS支持在批量推送时,给不同设备传递不同的badge值,但这需要服务器知道每个设备的当前角标数,本质还是要维护设备级计数,只是APNS提供了批量替换的接口,比逐个循环稍高效,但还是不如方案一彻底。
内容的提问来源于stack exchange,提问作者alionthego




