后台获取/蓝牙同步时UNUserNotification无法触发的解决方案咨询
兄弟,我太懂这种新旧API切换踩坑的痛苦了!iOS 10换成UserNotifications框架后,很多旧习惯会导致通知发不出来,我帮你梳理几个最常见的问题和解决办法,你可以逐个排查:
1. 权限申请是否正确配置
iOS 10+不再用UIApplication.shared.registerUserNotificationSettings(_:)这套旧API了,必须用UNUserNotificationCenter主动请求权限,而且要明确申请需要的权限类型(alert、sound、badge这些)。你得在application(_:didFinishLaunchingWithOptions:)里加这段代码:
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in if let error = error { print("权限申请失败:\(error.localizedDescription)") } // 这里可以根据granted状态做后续处理 }
另外,别忘了在Info.plist里添加NSUserNotificationUsageDescription(iOS 10+)或者NSAlertUsageDescription(iOS 12+),说明为什么需要通知权限,否则系统会直接拒绝申请。
2. UNUserNotificationCenterDelegate 是否正确设置
你已经实现了UNUserNotificationCenterDelegate,但有没有在启动时把代理绑定到中心?而且要注意,必须在主线程设置代理,不然可能在后台场景下代理不生效:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 其他初始化代码... UNUserNotificationCenter.current().delegate = self // 如果需要在前台显示通知,还要实现这个代理方法: // func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { // completionHandler([.alert, .sound]) // } return true }
如果是后台触发的通知,代理的userNotificationCenter(_:didReceive:withCompletionHandler:)也要正确实现,处理通知点击事件,不过这一步不影响通知的触发,主要是权限和代理绑定的问题。
3. 后台触发时是否在主线程调用通知API
当你在application(_:performFetchWithCompletionHandler:)(后台fetch)或者蓝牙同步的回调里发通知时,这些回调大概率是在子线程执行的,而UNUserNotificationCenter.addNotificationRequest(_:withCompletionHandler:)必须在主线程调用!你需要把通知代码包在DispatchQueue.main.async里:
// 比如在后台fetch方法里: func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // 模拟后台获取数据 fetchDataFromServer { result in // 数据获取完成,准备发通知 DispatchQueue.main.async { let content = UNMutableNotificationContent() content.title = "后台更新提醒" content.body = "数据已同步完成" content.sound = UNNotificationSound.default let request = UNNotificationRequest(identifier: "BackgroundUpdateNotification", content: content, trigger: nil) UNUserNotificationCenter.current().add(request) { error in if let error = error { print("添加通知请求失败:\(error.localizedDescription)") } } } completionHandler(.newData) } }
这是很多人踩的坑——后台任务默认在子线程,直接调用通知API会静默失败,没有任何报错!
4. 通知内容是否符合要求
iOS 10+对通知内容的校验更严格,比如:
- 必须设置
title或body至少一个,不然通知会被系统忽略 - 如果设置了自定义sound,要确保文件存在且格式正确,否则可能导致通知不显示
- 避免在
userInfo里放过大的数据,虽然不会直接导致失败,但可能影响通知的处理
5. 后台模式是否正确配置
虽然旧版本正常,但还是要确认Info.plist里的UIBackgroundModes(后台模式)有没有正确添加:
- 后台fetch需要添加
fetch - 蓝牙同步需要添加
bluetooth-central(作为中心设备)或bluetooth-peripheral(作为外设)
如果后台模式没开,系统可能不会让你的应用在后台执行任务,自然也发不出通知。
6. 测试方式是否正确
- 测试后台fetch:不要直接杀应用,要让应用进入后台,然后在Xcode的Debug菜单里选择
Simulate Background Fetch,或者在真机上等待系统触发(大概15-30分钟一次,取决于系统调度) - 测试蓝牙同步:确保蓝牙设备在后台保持连接,并且触发了同步回调,不要在前台测试后台场景
最后,排查错误日志
你可以在Xcode的Console里搜索UNUserNotificationCenter相关的日志,或者在addNotificationRequest的completionHandler里打印错误信息,这样能直接看到为什么通知没添加成功。
内容的提问来源于stack exchange,提问作者Bruno

