Swift实现应用进入后台/终止时向服务器发送请求的方案
首先得说,你之前尝试用NotificationCenter观察者来监听UIApplicationWillTerminate其实没必要——AppDelegate本身就提供了对应的生命周期回调方法,直接重写这些方法就行,这也是你观察者没生效的核心原因之一。接下来我会一步步帮你调整代码,实现需求:
核心问题分析
当App进入后台或即将终止时,系统会很快挂起App,普通的网络请求大概率会被中断。所以我们需要申请后台任务时间来保证请求能完成;另外applicationWillTerminate的触发场景很有限(只有用户手动划掉App且系统资源充足时才会调用),所以重点要放在applicationDidEnterBackground上。
步骤1:修改后台请求逻辑(添加后台任务)
更新applicationDidEnterBackground和applicationWillTerminate方法,给网络请求争取足够的后台执行时间:
func applicationDidEnterBackground(_ application: UIApplication) { var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid // 启动后台任务,告诉系统"我还有活儿没干完" backgroundTaskID = application.beginBackgroundTask(withName: "SendBackgroundRequest") { // 任务超时,必须结束后台任务,否则系统会终止App application.endBackgroundTask(backgroundTaskID) backgroundTaskID = .invalid } // 在全局队列执行请求,避免阻塞主线程 DispatchQueue.global().async { [weak self] in guard let self = self else { return } // 发送请求,注意要给getLiveMatches加回调,方便任务结束后通知系统 if let vc = self.window?.rootViewController as? LiveScores { vc.getLiveMatches(url: URL(string: "http://opucukgonder.com/tipster/index.php/Service/lastLive")) { // 请求完成,结束后台任务 application.endBackgroundTask(backgroundTaskID) backgroundTaskID = .invalid } } else { // 无法获取VC时直接结束任务 application.endBackgroundTask(backgroundTaskID) backgroundTaskID = .invalid } self.mS.StartTimer() } } func applicationWillTerminate(_ application: UIApplication) { var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid backgroundTaskID = application.beginBackgroundTask(withName: "SendTerminateRequest") { application.endBackgroundTask(backgroundTaskID) backgroundTaskID = .invalid } DispatchQueue.global().async { [weak self] in guard let self = self else { return } if let vc = self.window?.rootViewController as? LiveScores { vc.getLiveMatches(url: URL(string: "http://opucukgonder.com/tipster/index.php/Service/lastLive")) { application.endBackgroundTask(backgroundTaskID) backgroundTaskID = .invalid } } else { application.endBackgroundTask(backgroundTaskID) backgroundTaskID = .invalid } self.mS.StartTimer() } }
步骤2:更新getLiveMatches方法(添加完成回调)
你的getLiveMatches需要支持异步回调,这样才能在请求完成后结束后台任务。修改LiveScoresViewController里的方法:
func getLiveMatches(url: URL?, completion: @escaping () -> Void) { guard let requestUrl = url else { print("无效的URL") completion() return } URLSession.shared.dataTask(with: requestUrl) { data, response, error in defer { // 不管成功失败,都要调用completion结束后台任务 completion() } if let error = error { print("请求失败: \(error.localizedDescription)") return } guard let data = data else { print("未获取到响应数据") return } // 解析JSON数据(根据你的业务逻辑调整) do { let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) // 如果需要更新UI,必须切回主线程 DispatchQueue.main.async { // 这里写更新UI的逻辑,比如刷新赛事数据 } } catch { print("JSON解析失败: \(error.localizedDescription)") } }.resume() }
步骤3:配置后台模式
在Xcode中打开项目的Capabilities选项卡,找到Background Modes并勾选Remote notifications——这样App才能在后台接收服务器返回的推送通知,并且保证推送能正常展示。
步骤4:服务器端推送逻辑
当你的服务器收到App发送的请求后,需要通过OneSignal给对应的设备发送推送。确保服务器能获取到设备的OneSignal Player ID(你可以在App启动时通过OneSignal的API获取并上传到服务器),这样就能精准推送通知到目标设备。
注意事项
applicationWillTerminate的触发场景非常有限,大部分时候App被系统终止时不会调用这个方法,所以不要把核心逻辑依赖在这里,重点放在applicationDidEnterBackground。- 后台任务的时间是有限的(iOS 13+约30秒),所以你的请求要尽量轻量,避免耗时操作。
- 移除你之前添加的
UIApplicationWillTerminate观察者,直接用AppDelegate的回调方法即可。
内容的提问来源于stack exchange,提问作者Burhan Toprakman

