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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 12:42:08