iOS中后台持续执行网络请求与下载任务可行吗?如何实现?
iOS后台持续执行下载任务的实现方案
当然可行!不过iOS对后台任务的管控比较严格,不能像安卓那样无限制在后台跑,得用苹果官方提供的**后台URL会话(Background URL Session)**来实现——这玩意儿就是专门为长时间的上传/下载任务设计的,哪怕你的App退到后台甚至被系统暂停,任务也能继续执行直到完成。
下面给你拆解具体的实现步骤,都是实战里能用的方案:
1. 先创建后台专用的URLSession配置
首先得初始化一个带唯一标识符的后台配置,这个标识符很重要,系统靠它来识别你的后台任务,后续唤醒App的时候也会用到:
// 替换成你自己的唯一标识符,比如域名+功能名 let backgroundConfig = URLSessionConfiguration.background(withIdentifier: "com.yourapp.background.download") // 开启任务完成后唤醒App的功能(默认是true,建议保留) backgroundConfig.sessionSendsLaunchEvents = true // 创建会话,指定代理和全局队列(因为后台任务是系统托管的,不能用主队列) let backgroundSession = URLSession(configuration: backgroundConfig, delegate: self, delegateQueue: OperationQueue.global())
2. 启动你的下载任务
用刚才创建的后台会话来发起下载,用法和普通的URLSessionTask差不多:
if let downloadUrl = URL(string: "https://your-webservice-address/your-file") { let downloadTask = backgroundSession.downloadTask(with: downloadUrl) downloadTask.resume() }
3. 处理任务完成的回调
当下载任务完成(不管成功还是失败),系统会自动唤醒你的App(哪怕之前App被挂起了),你需要在两个地方处理回调:
第一步:在AppDelegate/SceneDelegate里保存系统的完成回调
// 以AppDelegate为例,SceneDelegate写法类似 func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { // 建议用一个单例来保存这个completionHandler,比如BackgroundSessionManager BackgroundSessionManager.shared.completionHandler = completionHandler }
这里的completionHandler必须在所有后台任务的代理回调完成后调用,告诉系统“我处理完了,你可以继续管理App状态了”。
第二步:实现URLSession的代理方法
首先要实现URLSessionDelegate的方法,用来触发刚才保存的completionHandler:
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { DispatchQueue.main.async { if let handler = BackgroundSessionManager.shared.completionHandler { BackgroundSessionManager.shared.completionHandler = nil handler() } } }
然后实现URLSessionDownloadDelegate的方法,处理下载完成后的文件操作:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { // 下载的文件默认存在临时目录,需要移动到App的沙盒目录(比如Documents) guard let docsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { print("获取Documents目录失败") return } // 用服务器返回的文件名,或者自己指定 let destUrl = docsDir.appendingPathComponent(downloadTask.response?.suggestedFilename ?? "downloaded_file") do { // 如果目标路径已有文件,先删除 if FileManager.default.fileExists(atPath: destUrl.path) { try FileManager.default.removeItem(at: destUrl) } // 移动临时文件到目标路径 try FileManager.default.moveItem(at: location, to: destUrl) // 这里可以做下载完成的后续操作,比如通知用户(如果App在前台的话) } catch { print("文件处理失败:\(error.localizedDescription)") } }
4. 必须注意的几个坑
- 别用
beginBackgroundTask处理长时间下载:这个方法只给你几分钟的后台时间,超时系统就会杀任务,只适合短时间的收尾操作,不适合持续下载。 - 后台任务是系统托管的:App退到后台后,你没法实时获取下载进度,只有当任务完成或者有阶段性回调时,系统才会唤醒App处理。
- 不需要额外开启后台模式?其实后台URLSession不需要手动在Xcode里开启Background Modes的权限,不过保险起见,如果你希望系统更稳定地唤醒App,可以去项目的Signing & Capabilities里添加Background Modes,勾选Background processing。
- 如果App被系统强制杀死:比如内存不足被清了,任务完成后系统会重新启动你的App(在后台状态),这时你要确保代理方法能正确处理回调,别让App崩溃。
如果你的需求是调用webservice的普通请求(不是大文件下载),且请求时间很短,用beginBackgroundTask临时续命也可以,但如果是长时间的下载/上传,后台URLSession是唯一靠谱的方案。
内容的提问来源于stack exchange,提问作者Pravin
相关产品推荐
相关产品推荐

