Swift中FCM通知异常:前台可接收,APP终止后无通知
解决Swift中FCM通知在APP终止时无法触发距离判断的问题
看起来你遇到的核心问题是:APP前台时能正常接收FCM通知并执行距离判断逻辑,但APP终止后,这条逻辑完全没机会运行,导致符合条件的通知无法展示。下面我会一步步拆解问题原因,并给出具体的修复方案:
一、问题根源分析
FCM在iOS上的消息分为两种类型,不同类型在APP终止状态下的处理逻辑完全不同:
- 通知消息(Notification Message):APP终止时,系统会直接把通知展示在通知栏,根本不会触发你的
didReceiveRemoteNotification或任何自定义回调,你的距离计算逻辑自然无法执行。 - 数据消息(Data Message):如果配置正确,系统会唤醒APP在后台短暂运行,触发对应的回调,但你当前的代码没有处理APP终止时的启动参数,也没开启必要的后台权限。
你的当前实现是在收到FCM消息后才计算距离、展示本地通知,但APP终止时,这个触发链条断了。
二、具体修复步骤
1. 调整FCM消息Payload(后端配合)
要让APP终止时也能处理距离判断,必须使用带content_available标记的数据消息,后端发送的Payload应该类似这样:
{ "to": "用户的设备Token", "data": { "page": "1", "msg": "[{\"m\": \"天气提醒--您附近空气质量不佳--37.7749--122.4194--50\"}]" }, "content_available": true, "priority": "high" }
content_available: true:告诉APNs唤醒APP在后台处理这条消息priority: "high":确保消息被优先推送(弱网环境下更可靠)
如果用Firebase控制台测试,记得在「Advanced options」里勾选「Content available」。
2. 开启iOS后台推送权限
在Xcode中配置后台模式:
- 打开项目的
Signing & Capabilities面板 - 添加
Background Modes权限,勾选Remote notifications - 确保
Info.plist中UIBackgroundModes包含remote-notification(Xcode会自动帮你添加)
3. 处理APP终止时的启动参数
当APP被FCM数据消息唤醒启动时,通知数据会包含在didFinishLaunchingWithOptions的参数里,你需要在这里触发距离计算逻辑:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { FirebaseApp.configure() Messaging.messaging().delegate = self // 把代理设置移到这里,不要等到收到消息再设置 attemptRegisterForNotifications(application: application) // 处理APP终止时收到的FCM数据消息 if let remoteNotification = launchOptions?[.remoteNotification] as? [AnyHashable: Any] { parseReceivedNotification(userInfo: remoteNotification) } return true }
4. 优化代码可靠性(避免崩溃)
你的代码里有大量强制解包(!),很容易因数据格式错误导致崩溃,建议改成可选绑定:
// 修改parseReceivedNotification里的距离计算部分 guard let dataUserStorevalue = dataUserinfo1["msg"], let data = (dataUserStorevalue as AnyObject).data(using: String.Encoding.utf8.rawValue), let dataUservalueget = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String : Any]], let dataUservalue = dataUservalueget else { print("无效的消息数据") return } for dataUserinfo in dataUservalue { guard let str = dataUserinfo["m"] as? String else { continue } let items = str.components(separatedBy: "--") guard items.count >= 5, let heading = items.first, let message = items[safe: 1], let latStr = items[safe: 2], let latitude = Double(latStr), let lonStr = items[safe: 3], let longitude = Double(lonStr), let aqivalue = items[safe: 4], let savlatStr = UserDefaultUtilities.getLatitudeOrLongitude(key: CommonUtilities.LATITUDE), let savlat = Double(savlatStr), let savLongStr = UserDefaultUtilities.getLatitudeOrLongitude(key: CommonUtilities.LONGITUDE), let savLong = Double(savLongStr) else { print("消息格式错误") continue } let distance = mesaureDistance(startX: latitude, startY: longitude, endX: savLong, endY: savlat) if distance < 10 { self.notification = NotificationObject(heading: heading, message: message, aqi: aqivalue) var notificationArray = UserDefaultUtilities.NotificatiionsArray notificationArray.append(self.notification) CommonUtilities.showWeatherNotificationWithUserInfo(notification: notification, userInfo: global_userInfo ) } }
这里用到了一个数组安全访问的扩展(可以自己实现):
extension Array { subscript(safe index: Index) -> Element? { return indices.contains(index) ? self[index] : nil } }
5. 确保通知权限已正确获取
在attemptRegisterForNotifications里,要明确请求用户授权所有需要的通知权限:
func attemptRegisterForNotifications(application: UIApplication) { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in if let error = error { print("请求通知权限失败:\(error)") return } if granted { DispatchQueue.main.async { application.registerForRemoteNotifications() } } } }
三、验证流程
- 确保APP已获取通知权限和后台推送权限
- 后端发送带
content_available: true的数据消息 - 杀死APP,等待消息推送
- 检查是否在符合距离条件时展示了本地通知
内容的提问来源于stack exchange,提问作者saurabh tripathi




