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

SwiftUI应用中两种方式获取的FCM Token不一致问题排查

SwiftUI中FCM Token获取不一致的问题

我在SwiftUI应用里通过Messaging.messaging().fcmToken获取的Token,和MessagingDelegate回调里拿到的Token不一致,想知道原因和解决办法。


我的代码实现

1. App结构体中获取Token的方式

@main
struct iOSApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

    var body: some Scene {
        WindowGroup {
            
            let isLogin = UserDefaults.standard.bool(forKey: "isLogin")
            let isUser = UserDefaults.standard.bool(forKey: "isUser")
               
            let token = Messaging.messaging().fcmToken// 此处期望获取与回调一致的Token
            
            if(isLogin == false){
                LoginView(myFCMToken: token ?? "fcmioserrordan")
            }else
            {
                if(isUser == true){
                    UsersView(myFCMToken: token ?? "fcmioserrordan")
                }else{
                    RestaurantView(myFCMToken: token ?? "fcmioserrordan")
                }
            }
        }
    }
}

2. MessagingDelegate回调获取Token

extension AppDelegate: MessagingDelegate {
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        let deviceToken:[String: String] = ["token": fcmToken ?? ""]
        print("Device token: ", deviceToken) // 此Token可正常用于FCM通知测试
    }
}

完整AppDelegate代码

class AppDelegate: NSObject, UIApplicationDelegate {
    let gcmMessageIDKey = "gcm.message_id"

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        FirebaseApp.configure()
        Messaging.messaging().delegate = self

        if #available(iOS 10.0, *) {
          // 针对iOS 10的显示通知(通过APNS发送)
          UNUserNotificationCenter.current().delegate = self
          let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
          UNUserNotificationCenter.current().requestAuthorization(
            options: authOptions,
            completionHandler: {_, _ in })
        } else {
          let settings: UIUserNotificationSettings =
          UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
          application.registerUserNotificationSettings(settings)
        }

        application.registerForRemoteNotifications()
        return true
    }

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
      if let messageID = userInfo[gcmMessageIDKey] {
        print("Message ID: \(messageID)")
      }
      print(userInfo)
      completionHandler(UIBackgroundFetchResult.newData)
    }
}

UNUserNotificationCenterDelegate扩展代码

@available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
  // 接收iOS 10设备的显示通知
  func userNotificationCenter(_ center: UNUserNotificationCenter,
                              willPresent notification: UNNotification,
    withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    let userInfo = notification.request.content.userInfo
    if let messageID = userInfo[gcmMessageIDKey] {
        print("Message ID: \(messageID)")
    }
    print(userInfo)
    // 可修改为你偏好的展示选项
    completionHandler([[.banner, .badge, .sound]])
  }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {}

    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {}

  func userNotificationCenter(_ center: UNUserNotificationCenter,
                              didReceive response: UNNotificationResponse,
                              withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo
    if let messageID = userInfo[gcmMessageIDKey] {
      print("Message ID from userNotificationCenter didReceive: \(messageID)")
    }
    print(userInfo)
    completionHandler()
  }
}

问题原因

  1. 加载时机不匹配:App结构体的body会在应用启动初期就执行,此时Firebase可能还没完成初始化,fcmToken返回的是缓存的旧Token或空值;而didReceiveRegistrationToken回调是在Firebase成功获取/刷新Token后才触发,拿到的是最新有效Token。
  2. Token自动刷新机制:FCM Token会定期自动刷新,或在应用重装、设备系统更新等场景下变更。fcmToken属性可能缓存旧值,回调则会在Token更新时立即返回新值。
  3. APNS注册未完成:FCM依赖APNS设备Token生成自身Token,App启动时application.registerForRemoteNotifications()流程还没走完,导致fcmToken无法获取最新有效Token,而回调会等APNS注册完成后才拿到正确的FCM Token。

解决方案

方案1:回调中同步Token到UserDefaults

在didReceiveRegistrationToken回调里把最新Token存入UserDefaults,后续从UserDefaults读取:

extension AppDelegate: MessagingDelegate {
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        guard let token = fcmToken else { return }
        UserDefaults.standard.set(token, forKey: "latestFCMToken")
        print("Device token: ", token)
    }
}

修改App结构体中的Token获取逻辑:

let token = UserDefaults.standard.string(forKey: "latestFCMToken") ?? "fcmioserrordan"

方案2:用ObservableObject监听Token变化

创建全局Token管理类,通过@Published属性实时发布Token更新:

class FCMTokenManager: ObservableObject {
    @Published var currentToken: String?
    
    init() {
        // 初始化先读取缓存Token
        currentToken = UserDefaults.standard.string(forKey: "latestFCMToken")
        // 设置代理监听Token更新
        Messaging.messaging().delegate = self
    }
}

extension FCMTokenManager: MessagingDelegate {
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        currentToken = fcmToken
        if let token = fcmToken {
            UserDefaults.standard.set(token, forKey: "latestFCMToken")
        }
    }
}

在App结构体中注入管理器并使用:

@main
struct iOSApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    @StateObject private var tokenManager = FCMTokenManager()

    var body: some Scene {
        WindowGroup {
            let isLogin = UserDefaults.standard.bool(forKey: "isLogin")
            let isUser = UserDefaults.standard.bool(forKey: "isUser")
               
            if(isLogin == false){
                LoginView(myFCMToken: tokenManager.currentToken ?? "fcmioserrordan")
            }else
            {
                if(isUser == true){
                    UsersView(myFCMToken: tokenManager.currentToken ?? "fcmioserrordan")
                }else{
                    RestaurantView(myFCMToken: tokenManager.currentToken ?? "fcmioserrordan")
                }
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 19:35:24