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

iOS后台/关闭状态下通知检查函数无法运行的问题求助

后台/应用关闭时通知检查函数无法执行的问题排查与修复

我编写了一个检查数组新增通知的函数,前台运行正常,但后台或应用关闭时完全失效。已配置后台模式(Background Fetch、Remote Notifications、Background processing),在Info.plist中添加了后台任务标识符com.showfa.fetchNotifications,并导入BackgroundTasks框架,相关代码如下:

通知检查核心函数

func fetchNotifications() {
        guard let url = URL(string: "http://example.com") else {
            return
        }
        
        var request = URLRequest(url: url)
        request.setValue(accessTkn, forHTTPHeaderField: "Authorization")

        URLSession.shared.dataTask(with: request) { data, _, error in
            if let error = error {
                print("Error fetching data: \(error)")
                return
            }

            if let data = data {
                do {
                    let decoder = JSONDecoder()
                    let fetchedNotifications = try decoder.decode([Notification].self, from: data)
                    DispatchQueue.main.async {
                        self.notifications = fetchedNotifications
                        
                        if let decodedNotifications = self.loadNotificationsFromUserDefaults() {
                            let newValue = self.notifications.filter { newNoti in
                                !decodedNotifications.contains { newData in
                                    newNoti.message == newData.message
                                }
                            }
                            if newValue.isEmpty {
                                print("1")
                            } else {
                                for newNoti in newValue {
                                    self.notiCat = newNoti.category
                                    self.notiMess = newNoti.message
                                    self.sendPushNotificationIfNeeded(category: self.notiCat, message: self.notiMess)
                                }
                            }
                        } else {
                            print("E1")
                        }
                        
                        // Save notifications to UserDefaults
                        self.saveNotificationsToUserDefaults(notifications: self.notifications)
                    }
                } catch {
                    print("Error decoding data: \(error)")
                }
            }
        }.resume()
    }

AppDelegate相关代码

@main
struct CabsPointApp: App {
    let persistenceController = PersistenceController.shared
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    init() {
        // Request background task processing identifier
        BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.showfa.fetchNotifications", using: nil) { task in
            if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
               let appRefreshTask = task as? BGAppRefreshTask {
                appDelegate.handleAppRefreshTask(task: appRefreshTask)
            }
        }
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    var notificationViewModel = NotificationViewModel()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        UNUserNotificationCenter.current().delegate = self
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
            if granted {
                print("Notification authorization granted")
            } else {
                print("Notification authorization denied")
            }
        }
        
        scheduleBackgroundTask()
        
        return true
    }

}

extension AppDelegate {
    // Handle notification when the app is in the foreground
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.alert, .sound, .badge])
    }
    
    // Handle notification when the user taps on it
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        // Handle the user's action here
        completionHandler()
    }
    
    func scheduleBackgroundTask() {
        let request = BGAppRefreshTaskRequest(identifier: "com.showfa.fetchNotifications")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 3 * 1) // Start task 3 seconds from now
        
        do {
            try BGTaskScheduler.shared.submit(request)
            print("Scheduled background task for fetching notifications")
        } catch {
            print("Unable to schedule background task: \(error)")
        }
    }
    
    func handleAppRefreshTask(task: BGAppRefreshTask) {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1
        
        let operation = BlockOperation {
            self.notificationViewModel.startBackgroundTask()
            self.scheduleBackgroundTask()
            task.setTaskCompleted(success: true)
        }
        
        queue.addOperation(operation)
    }
}

核心问题与修复方案

1. 后台任务生命周期管理错误

当前handleAppRefreshTask在异步请求未完成时就标记任务完成,系统会直接终止后续操作。

  • 修复:给异步函数添加完成回调,确保所有逻辑执行完毕后再标记任务完成,同时设置过期处理避免系统强制终止。

更新NotificationViewModel的相关函数:

func startBackgroundTask(completion: @escaping () -> Void) {
    fetchNotifications(completion: completion)
}

func fetchNotifications(completion: @escaping () -> Void) {
    guard let url = URL(string: "http://example.com") else {
        completion()
        return
    }
    
    var request = URLRequest(url: url)
    request.setValue(accessTkn, forHTTPHeaderField: "Authorization")

    URLSession.shared.dataTask(with: request) { data, _, error in
        defer { completion() }
        if let error = error {
            print("Error fetching data: \(error)")
            return
        }

        if let data = data {
            do {
                let decoder = JSONDecoder()
                let fetchedNotifications = try decoder.decode([Notification].self, from: data)
                DispatchQueue.main.sync {
                    self.notifications = fetchedNotifications
                    
                    if let decodedNotifications = self.loadNotificationsFromUserDefaults() {
                        let newValue = self.notifications.filter { newNoti in
                            !decodedNotifications.contains { $0.message == newNoti.message }
                        }
                        newValue.forEach {
                            self.sendPushNotificationIfNeeded(category: $0.category, message: $0.message)
                        }
                    }
                    self.saveNotificationsToUserDefaults(notifications: self.notifications)
                }
            } catch {
                print("Error decoding data: \(error)")
            }
        }
    }.resume()
}

修改handleAppRefreshTask:

func handleAppRefreshTask(task: BGAppRefreshTask) {
    task.expirationHandler = {
        task.setTaskCompleted(success: false)
        print("Background task expired before completion")
    }
    
    let queue = OperationQueue()
    queue.maxConcurrentOperationCount = 1
    
    let operation = BlockOperation {
        let semaphore = DispatchSemaphore(value: 0)
        self.notificationViewModel.startBackgroundTask {
            semaphore.signal()
        }
        semaphore.wait()
        
        self.scheduleBackgroundTask()
        task.setTaskCompleted(success: true)
    }
    
    queue.addOperation(operation)
}

2. 后台任务调度间隔不合理

iOS会忽略过短的调度间隔(如3秒),实际不会触发任务。

  • 修复:设置合理的间隔(如15分钟),系统会根据用户使用习惯优化触发时机:
func scheduleBackgroundTask() {
    let request = BGAppRefreshTaskRequest(identifier: "com.showfa.fetchNotifications")
    request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 15)
    
    do {
        try BGTaskScheduler.shared.submit(request)
        print("Scheduled background task for fetching notifications")
    } catch {
        print("Unable to schedule background task: \(error)")
    }
}

3. 应用关闭时的任务限制

应用完全关闭后,BGAppRefreshTask不会触发,需依赖APNs静默推送唤醒应用。

  • 补充方案:服务器发送带content-available: 1的静默推送,在AppDelegate中处理:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    notificationViewModel.fetchNotifications {
        completionHandler(.newData)
    }
}

4. 其他细节优化

  • 确保Info.plist的BGTaskSchedulerPermittedIdentifiers数组包含com.showfa.fetchNotifications;
  • 保证UserDefaults的读写操作都在主线程,避免线程安全问题;
  • 确认Xcode的Signing & Capabilities中已正确勾选Background Modes下的Background fetch和Remote notifications。

内容的提问来源于stack exchange,提问作者CodeMan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 01:58:12