Swift中获取Device Token并通过WebView URL传递存储的解决方案
实现将Device Token通过WebView GET参数传递并存储的方案
核心说明
Device Token(APNs设备令牌)和UUID确实不同:UUID是设备的唯一标识符(iOS现在限制直接获取),而Device Token是苹果推送服务(APNs)给设备颁发的专属推送令牌,卸载重装App后可能会变化,必须实时获取并同步到后端。
分步实现方案
1. 修复AppDelegate中的重复方法问题
你的AppDelegate里存在两个didRegisterForRemoteNotificationsWithDeviceToken方法,这会导致编译错误。需要合并成一个,同时正确生成Device Token字符串,并通过通知告知ViewController Token已获取。
修改后的AppDelegate.swift:
import UIKit import FirebaseCore import FirebaseMessaging import UserNotifications @main class AppDelegate: UIResponder, UIApplicationDelegate { let gcmMessageIDKey = "gcm.Message_ID" var deviceTokenString: String? var fcmTokenString: String? // 合并后的Device Token获取方法 func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { // 生成标准小写格式的Device Token(和后端常见解析格式一致) deviceTokenString = deviceToken.map { String(format: "%02x", $0) }.joined() print("===== Device Token =====") print(deviceTokenString ?? "未获取到Device Token") // 发送通知通知ViewController Token已就绪 if let token = deviceTokenString { NotificationCenter.default.post( name: Notification.Name("DeviceTokenReceived"), object: nil, userInfo: ["deviceToken": token] ) } } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { FirebaseApp.configure() // 推送权限配置 if #available(iOS 10.0, *) { 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() Messaging.messaging().delegate = self return true } func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {} } extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { let userInfo = notification.request.content.userInfo print(userInfo) completionHandler([.alert, .sound]) } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let userInfo = response.notification.request.content.userInfo print(userInfo) completionHandler() } 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) } } extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { fcmTokenString = fcmToken print("===== FCM Token =====") print(fcmTokenString ?? "未获取到FCM Token") // 如需传递FCM Token,可发送对应通知 if let token = fcmTokenString { NotificationCenter.default.post( name: Notification.Name("FCMTokenReceived"), object: nil, userInfo: ["fcmToken": token] ) } } }
2. 修改ViewController监听Token并加载WebView
Device Token的获取是异步操作,viewDidLoad执行时可能Token还未就绪,因此需要通过NotificationCenter监听Token获取完成的事件,再动态拼接URL加载WebView。
修改后的ViewController.swift:
import UIKit import WebKit class ViewController: UIViewController { let webView = WKWebView() // 后端基础接口地址 let baseURLString = "https://myurl.be/app.php" override func viewDidLoad() { super.viewDidLoad() view.addSubview(webView) // 用AutoLayout约束WebView(替代viewDidLayoutSubviews的写法更规范) webView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ webView.topAnchor.constraint(equalTo: view.topAnchor), webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), webView.trailingAnchor.constraint(equalTo: view.trailingAnchor), webView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) // 监听Device Token就绪的通知 NotificationCenter.default.addObserver( self, selector: #selector(handleDeviceTokenReceived(_:)), name: Notification.Name("DeviceTokenReceived"), object: nil ) // 检查App重启时是否已缓存Token if let appDelegate = UIApplication.shared.delegate as? AppDelegate, let token = appDelegate.deviceTokenString { loadWebViewWithToken(token: token) } } @objc private func handleDeviceTokenReceived(_ notification: Notification) { guard let token = notification.userInfo?["deviceToken"] as? String else { return } loadWebViewWithToken(token: token) // 移除监听避免重复触发 NotificationCenter.default.removeObserver(self, name: Notification.Name("DeviceTokenReceived"), object: nil) } private func loadWebViewWithToken(token: String) { // 对Token做URL编码,防止特殊字符破坏参数结构 guard let encodedToken = token.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: "\(baseURLString)?device=\(encodedToken)") else { return } print("加载的目标URL: \(url.absoluteString)") webView.load(URLRequest(url: url)) } deinit { // 页面销毁时清理通知监听,避免内存泄漏 NotificationCenter.default.removeObserver(self) } }
关键注意事项
- 必须做URL编码:Device Token是十六进制字符串,虽然本身没有特殊字符,但编码后能避免潜在的参数解析错误,确保后端能正确接收。
- 异步处理Token:Token获取是异步的,不能在
viewDidLoad中直接同步拼接URL,必须等回调完成后再加载WebView。 - FCM Token替代选择:如果用Firebase推送服务,推荐传递FCM Token而非APNs的Device Token,因为FCM Token是Firebase推送直接使用的令牌,兼容性更好。只需把代码中的
DeviceTokenReceived通知替换为FCMTokenReceived即可。
内容的提问来源于stack exchange,提问作者Patrick De Wolf
相关产品推荐
相关产品推荐

