为何didReceiveRemoteNotification方法仅在Xcode调试时被调用?
didReceiveRemoteNotification仅在Xcode调试时触发,非调试状态无响应 我正在用Swift 4开发iOS 11应用,通过Firebase Cloud Messaging(FCM)实现推送通知功能。已经完成了以下配置流程:
- 加入Apple Developer计划,注册App ID并生成带有APNs服务的密钥
- 将iOS应用关联到现有Firebase项目,上传上述生成的Apple密钥
- 配置应用接收通知:添加
GoogleServices-Info.plist文件,安装Firebase/Core和Firebase/Messaging这两个pod依赖 - 在Xcode中启用必要的Capabilities:Background Modes(勾选Remote notifications)和Push Notifications
- 编写
AppDelegate.swift的通知处理逻辑,代码如下:
import UIKit import CoreData import KeychainAccess import IQKeyboardManagerSwift import Fabric import Crashlytics import Firebase import FirebaseMessaging import FirebaseInstanceID import UserNotifications @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. //FIREBASE CONFIGURATION START FirebaseApp.configure() UNUserNotificationCenter.current().requestAuthorization( options: [.alert, .badge, .sound]) { (success, error) in if error == nil { print("Successfull authorization!") } } application.registerForRemoteNotifications() NotificationCenter.default.addObserver(self, selector: #selector(refreshToken(notification:)), name: NSNotification.Name.InstanceIDTokenRefresh, object: nil) UNUserNotificationCenter.current().delegate = self //FIREBASE CONFIGURATION END if let token = InstanceID.instanceID().token() { print("token: '\(token)'") } return true } func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. Messaging.messaging().shouldEstablishDirectChannel = false } func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. FBHandler() } @objc func refreshToken(notification: NSNotification) { let refreshToken = InstanceID.instanceID().token()! print("*** \(refreshToken) ***") FBHandler() } func FBHandler() { Messaging.messaging().shouldEstablishDirectChannel = true } // MARK: - Handle Silent FCM notifications func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { showLocalNotification() print("Entire message \(userInfo)") let state : UIApplicationState = application.applicationState switch state { case UIApplicationState.active: print("If needed notify user about the message") default: print("Run code to download content") } completionHandler(UIBackgroundFetchResult.newData) } func showLocalNotification() { let notification = UNMutableNotificationContent() notification.title = "Nou contingut disponible: Benicolet" notification.body = "Restriccions cremes agrícoles" let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false) let request = UNNotificationRequest(identifier: "\(Int(arc4random_uniform(999999)))", content: notification, trigger: notificationTrigger) UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) } } // Make local notification to appear even when app is open extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { print(response.notification.request.content.userInfo) completionHandler() } func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.alert, .badge, .sound]) } }
现在遇到的问题是:didReceiveRemoteNotification(_:fetchCompletionHandler:)方法只有在Xcode调试运行时才会被正常调用,当我打包成Ad Hoc或者App Store发布版本(脱离Xcode调试状态)时,这个方法完全无法触发,后台通知接收失效。
解决思路与方案
1. 核对APNs环境与推送目标匹配
FCM的推送环境必须和你的应用构建环境严格对应:
- Xcode调试时,应用使用**Sandbox(开发)**APNs环境
- 发布版本(Ad Hoc/App Store)使用**Production(生产)**APNs环境
操作步骤:
- 登录Firebase控制台,进入项目设置 → 云消息传递(Cloud Messaging)
- 确认你上传的Apple密钥支持双环境(或者分别配置了开发/生产的证书)
- 测试推送时,确保发送的通知指定了正确的环境:
- 调试版本用
development环境推送 - 发布版本用
production环境推送(如果用Firebase控制台发送,在高级选项里选择对应的环境)
- 调试版本用
2. 检查Background Modes配置是否正确
确保Xcode的Capabilities中,Background Modes仅勾选Remote notifications,不要多余勾选其他选项,避免系统限制后台行为。同时检查Info.plist中是否存在正确的配置:
<key>UIBackgroundModes</key> <array> <string>remote-notification</string> </array>
3. 确保通知Payload包含content-available字段
要触发didReceiveRemoteNotification(_:fetchCompletionHandler:),你的FCM通知Payload必须包含content-available: 1(APNs格式)或者content_available: true(FCM格式,Firebase会自动转换)。
正确的FCM Payload示例:
{ "to": "YOUR_DEVICE_TOKEN", "content_available": true, "notification": { "title": "Test", "body": "Background notification test" }, "data": { "key": "value" } }
如果用Firebase控制台发送,在高级选项的自定义数据里添加content_available: true即可。
4. 验证应用的通知权限状态
非调试状态下,用户可能未授予通知权限或者权限被撤销。可以在应用启动时添加权限检查逻辑,确保权限有效:
UNUserNotificationCenter.current().getNotificationSettings { settings in guard settings.authorizationStatus == .authorized else { // 重新请求权限 UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, _ in if success { UIApplication.shared.registerForRemoteNotifications() } } return } }
5. 检查Provisioning Profile权限
确保发布版本使用的Provisioning Profile已经启用了Push Notifications服务,并且是针对当前App ID生成的。可以在Apple Developer后台查看Profile的详细信息,确认Push Notifications已勾选。
6. 注意后台任务的时间限制
非调试状态下,系统对后台任务的限制更严格,didReceiveRemoteNotification(_:fetchCompletionHandler:)必须在30秒内调用completionHandler,否则系统会终止你的应用进程。确保你在方法内没有执行耗时操作,及时结束任务。
内容的提问来源于stack exchange,提问作者Isaac Bosca

