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

如何检测iOS APNs推送Token变化并仅在变更时更新服务器

无需缓存检测APNs Token变化并更新服务器的方案

核心思路

苹果禁止的是内存缓存或临时存储设备Token(避免Token变更后仍使用旧值),但可以用Keychain持久化存储之前的Token用于对比——Keychain是系统级安全存储,不属于苹果定义的“缓存”范畴,且能在应用重启、升级后保留数据,完美满足对比需求。

具体实现步骤

  • 1. 规范Token字符串格式
    在didRegisterForRemoteNotificationsWithDeviceToken回调中,将苹果返回的Data类型Token转换为统一格式的字符串,避免因格式差异导致误判:

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
        // 后续对比、上传逻辑
    }
    
  • 2. 封装Keychain读写工具
    写一个轻量的Keychain工具类,专门用于存储和读取设备Token,确保只有当前应用可访问:

    struct KeychainManager {
        private static let tokenKey = "APNsDeviceToken"
        
        static func saveToken(_ token: String) -> Bool {
            let data = token.data(using: .utf8)!
            let query: [CFString: Any] = [
                kSecClass: kSecClassGenericPassword,
                kSecAttrAccount: tokenKey,
                kSecValueData: data,
                kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
            ]
            SecItemDelete(query as CFDictionary)
            return SecItemAdd(query as CFDictionary, nil) == errSecSuccess
        }
        
        static func getToken() -> String? {
            let query: [CFString: Any] = [
                kSecClass: kSecClassGenericPassword,
                kSecAttrAccount: tokenKey,
                kSecReturnData: kCFBooleanTrue!,
                kSecMatchLimit: kSecMatchLimitOne
            ]
            var data: AnyObject?
            let status = SecItemCopyMatching(query as CFDictionary, &data)
            guard status == errSecSuccess, let tokenData = data as? Data else { return nil }
            return String(data: tokenData, encoding: .utf8)
        }
    }
    
  • 3. 对比Token并触发更新
    在拿到新Token后,读取Keychain中的旧Token,对比后决定是否请求服务器:

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let newToken = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
        let oldToken = KeychainManager.getToken()
        
        guard newToken != oldToken else {
            // Token未变化,无需更新
            return
        }
        
        // 调用服务器接口更新Token
        updateTokenOnServer(newToken) { success in
            if success {
                // 仅在服务器更新成功后,更新Keychain中的Token
                _ = KeychainManager.saveToken(newToken)
            }
        }
    }
    

关键注意事项

  • 服务器端做幂等处理:即使因极端情况(如Keychain读取失败)发送了重复Token,服务器要忽略重复请求,避免数据异常。
  • Keychain权限设置:使用kSecAttrAccessibleWhenUnlockedThisDeviceOnly确保Token仅在设备解锁时可访问,且不会同步到iCloud(若需跨设备同步可调整权限,但不推荐)。
  • 处理Token获取失败场景:在didFailToRegisterForRemoteNotificationsWithError中记录错误,避免遗漏注册流程。

内容的提问来源于stack exchange,提问作者Alisher Baigazin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 04:11:08