You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在iOS NotificationService扩展中检测主应用状态以管理未读消息角标

如何在iOS NotificationService扩展中检测主应用状态以管理未读消息角标

我之前帮不少开发者处理过iOS消息APP的角标计数问题,太懂你这种痛点了——前台后台的时候能轻松拿服务器的准确数,但APP彻底退出就抓瞎,只能靠NotificationService扩展来处理,可扩展又没法直接知道主应用是不是真的终止了对吧?

先再明确下你的场景:

开发iOS消息APP时,APP在前台/后台运行时,能通过didReceiveRemoteNotification或者userNotificationCenter(_:willPresent:)方法查询服务器,拿到准确的未读消息数来更新角标;但当APP完全终止时,只能依赖NotificationService扩展,这时候不知道怎么判断主应用状态,没法精准管理角标。

下面给你几个实用的解决思路,都是实际项目里验证过的:

1. 用共享UserDefaults标记应用状态(简单但有局限)

主应用正常退出的时候,会触发applicationWillTerminate(_:)方法,我们可以在这里把“应用已终止”的标记存在共享的UserDefaults里(要开启App Groups,让主应用和扩展能共享数据)。然后NotificationService扩展就读这个标记,判断主应用是不是真的没在运行。

代码示例:

主应用里(AppDelegate.swift):

func applicationWillTerminate(_ application: UIApplication) {
    // 替换成你的App Group ID,要和扩展里的一致
    let sharedDefaults = UserDefaults(suiteName: "group.com.yourapp.notification")
    sharedDefaults?.set(true, forKey: "AppIsTerminated")
    sharedDefaults?.synchronize()
}

// 主应用启动时要重置标记,避免下次启动后扩展误判
func applicationDidFinishLaunching(_ application: UIApplication) {
    let sharedDefaults = UserDefaults(suiteName: "group.com.yourapp.notification")
    sharedDefaults?.set(false, forKey: "AppIsTerminated")
    sharedDefaults?.synchronize()
}

NotificationService扩展里:

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler
    bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
    
    let sharedDefaults = UserDefaults(suiteName: "group.com.yourapp.notification")
    let isAppTerminated = sharedDefaults?.bool(forKey: "AppIsTerminated") ?? false
    
    if isAppTerminated {
        // 主应用确实终止了,这里可以发起轻量网络请求拿最新未读数,或者用推送payload里的badge值
        // 注意:扩展运行时间有限,网络请求超时要设短一点(比如5秒)
        fetchUnreadCountAndUpdateBadge()
    } else {
        // 主应用可能在后台,这时候可以选择不更新角标,等主应用自己处理
        contentHandler(bestAttemptContent ?? request.content)
    }
}

private func fetchUnreadCountAndUpdateBadge() {
    guard let url = URL(string: "https://your-server.com/api/unread-count") else {
        contentHandler(bestAttemptContent ?? UNNotificationContent())
        return
    }
    
    var urlRequest = URLRequest(url: url)
    urlRequest.timeoutInterval = 5
    
    let task = URLSession.shared.dataTask(with: urlRequest) { [weak self] data, _, error in
        guard let self = self, let data = data, error == nil else {
            // 请求失败,用payload里的badge兜底
            self.contentHandler(self.bestAttemptContent ?? UNNotificationContent())
            return
        }
        
        // 解析服务器返回的未读数,这里假设返回的是单个数字
        if let unreadCount = try? JSONSerialization.jsonObject(with: data) as? Int {
            self.bestAttemptContent?.badge = NSNumber(value: unreadCount)
        }
        self.contentHandler(self.bestAttemptContent ?? UNNotificationContent())
    }
    task.resume()
}

⚠️ 注意:如果APP是被系统强制杀死的(比如内存不足、用户在后台划掉后系统清理),applicationWillTerminate可能不会被调用,这时候标记就不准确了,所以这个方法适合作为辅助,最好结合下面的思路。

2. 直接从服务器拉取最新未读数(最准确)

其实不管主应用是不是终止,在NotificationService扩展里发起一个轻量的网络请求,直接从服务器拿当前用户的最新未读总数,然后设置角标,这是最靠谱的方式。毕竟角标的准确性才是核心,判断主应用状态只是为了要不要做这个操作而已。

这里要注意几个点:

  • 扩展的运行时间有限(大概30秒,实际建议控制在10秒内),所以网络请求的超时时间一定要短,避免被系统强制终止。
  • 尽量用GET请求,减少数据传输量,加快响应速度。
  • 如果请求失败,就用推送payload里的badge字段兜底,保证角标至少有个合理的数值。

3. 用已推送通知数辅助判断(可选)

你还可以在扩展里调用UNUserNotificationCenter.current().getDeliveredNotifications,获取当前设备上已展示的推送数量,结合服务器的未读数来校验,但这个方法只能拿到本地的推送记录,和服务器的实际未读数可能有差异,适合作为补充手段。

最后再提几个关键注意事项:

  • NotificationService扩展是独立的进程,不能直接访问主应用的内存状态,所有状态判断都只能通过共享存储、网络请求这些间接方式。
  • 主应用一旦启动,一定要立刻从服务器拉取最新未读数,覆盖扩展设置的角标,确保多设备同步或者扩展请求失败时的准确性。
  • 配置App Groups时,要在Xcode里的主应用和扩展的「Capabilities」面板里都开启,并且用同一个Group ID。

备注:内容来源于stack exchange,提问作者laconicman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 17:09:34