DispatchQueue异步任务前后台存活问题及Firebase Token更新最佳实践
问题分析与解决方案
普通的DispatchQueue.global(qos: .background).async或主队列异步任务,在App进入后台后会被系统快速挂起,被终止后更是直接停止,根本没法保证执行。要实现后台持久执行,必须依赖iOS官方提供的后台机制,而非自行用GCD队列处理。
一、短时间后台操作:用后台任务断言
如果Token更新是几秒内能完成的短任务,可以通过UIApplication.shared.beginBackgroundTask申请额外后台执行时间:
func updateFirebaseToken() { var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid // 申请后台任务 backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "FirebaseTokenUpdate") { [weak self] in // 超时未完成,主动结束任务避免被系统杀死 UIApplication.shared.endBackgroundTask(backgroundTaskID) backgroundTaskID = .invalid } DispatchQueue.global(qos: .background).async { [weak self] in defer { // 任务完成后必须结束后台任务 if backgroundTaskID != .invalid { UIApplication.shared.endBackgroundTask(backgroundTaskID) backgroundTaskID = .invalid } } // 执行Firebase Token更新逻辑 if let token = Messaging.messaging().fcmToken { // 上传Token到你的服务器 // ... } } }
注意:这种方式最多能拿到约30秒的后台执行时间,超时系统会强制终止任务。
二、周期性后台更新:用后台刷新任务
如果需要定期在后台更新Token(比如Token过期前),可以开启后台刷新权限:
- 在Xcode的
Signing & Capabilities中添加Background Modes,勾选Background fetch - 代码配置刷新间隔:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 设置最小刷新间隔,由系统决定实际触发时机 UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum) return true }
- 实现后台刷新回调:
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // 检查Token是否需要更新 if shouldUpdateFirebaseToken() { updateFirebaseToken() completionHandler(.newData) } else { completionHandler(.noData) } }
注意:系统会根据用户使用习惯、设备电量等因素决定是否触发刷新,无法精确控制时间。
三、最优方案:利用Firebase原生回调处理Token更新
Firebase本身提供了Token变化的回调,不需要手动轮询或后台执行:
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { guard let token = fcmToken else { return } // 直接上传Token到服务器 // 这个回调会在Token生成、更新时自动触发,即使App在后台(只要有网络) }
当App被终止后,如果Token发生变化,Firebase会在下次App启动时触发该回调,所以启动时也需要检查一次:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { if let token = Messaging.messaging().fcmToken { uploadTokenToServer(token) } return true }
后台运行最佳实践
- 优先用系统原生后台机制,别自己用GCD队列模拟后台执行,系统会直接挂起这类任务
- 后台任务要尽可能轻量化,避免长时间占用资源,否则会被系统标记为不良行为,后续后台权限会被限制
- 对于Token这类无需频繁更新的内容,依赖Firebase原生回调而非周期性后台任务,减少后台唤醒次数
- 务必处理后台任务的结束逻辑(比如
endBackgroundTask),否则系统会认为App滥用后台权限,直接杀死进程 - 若需长时间后台运行(如导航、音频类App),要申请对应后台模式权限,但Token更新不需要这类权限,别乱申请
内容的提问来源于stack exchange,提问作者Aswin Rathees
相关产品推荐
相关产品推荐

