URLSession 系列第六篇:文件传输新姿势,教你玩转 URLSession 后台任务

这里每天分享一个 iOS 的新知识,快来关注我吧

前言

在前几篇文章中,我们探讨了 URLSession 的介绍、基本使用和安全性等等。

看到有小伙伴留言让讲讲后台下载相关的内容,那本篇文章就来重点介绍 URLSession 的后台传输任务,帮助你在应用处于后台时继续进行文件下载和上传操作。

后台传输任务对于需要处理大文件传输的应用非常重要,尤其是在需要保障传输过程不中断的情况下。

在阅读这篇文章之前,建议你先阅读我们前几篇关于 URLSession 的文章,这篇文章的代码都是基于前几篇的,看完你会更容易理解本文的内容。

URLSession 系列第一篇:iOS 开发者必须掌握的 URLSession 使用指南

URLSession 系列第二篇:手把手教你封装超实用的网络工具类

URLSession 系列第三篇:告别回调地狱,掌握 Completion Handlers 和 async/await!

URLSession 系列第四篇:彻底防止中间人攻击,确保网络安全!

URLSession 系列第五篇:如何精准监控 HTTPS 请求性能,优化必备!

什么是后台传输任务?

后台传输任务是指在应用处于后台状态时,仍然可以继续进行的网络传输任务(如文件下载和上传)。iOS 提供了 URLSessionConfigurationbackground 选项,允许我们创建支持后台传输的 URLSession

后台传输任务有哪些具体的应用场景:

  • 下载大文件(如视频、音频、PDF 等),如果只支持前台下载,用户需要一直保持在前台,体验会很差

  • 上传大文件(如用户生成的内容)

  • 保证传输任务即使在应用被挂起或终止时也能继续进行

创建后台传输任务

要使用 URLSession 的后台传输任务,我们需要完成以下几个步骤:

  1. 创建一个支持后台传输的 URLSessionConfiguration

  2. 实现 URLSessionDelegate 以处理后台任务完成的回调。

  3. 创建和管理后台传输任务。

  4. 申请 App 在后台运行,以确保下载任务不会被终止。

实现步骤

1. 创建后台传输的 URLSessionConfiguration

我们首先需要创建一个 URLSessionConfiguration 并设置其 identifier,它的作用是在后台任务完成时能够正确地恢复会话。

swift 复制代码
import Foundation

class BackgroundSessionManager: NSObject {
    static let shared = BackgroundSessionManager()
    private var session: URLSession!
    
    private override init() {
        super.init()
        let configuration = URLSessionConfiguration.background(withIdentifier: "com.yourapp.backgroundSession")
        session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
    }
    
    func startDownload(url: URL) {
        let downloadTask = session.downloadTask(with: url)
        downloadTask.resume()
    }
    
    func startUpload(fileURL: URL, to serverURL: URL) {
        var request = URLRequest(url: serverURL)
        request.httpMethod = "POST"
        let uploadTask = session.uploadTask(with: request, fromFile: fileURL)
        uploadTask.resume()
    }
}

2. 实现 URLSessionDelegate 处理后台任务完成的回调

接下来,我们需要实现 URLSessionDelegate 的相关方法,处理后台任务完成的回调。

swift 复制代码
extension BackgroundSessionManager: URLSessionDelegate, URLSessionDownloadDelegate, URLSessionTaskDelegate {
    // 处理下载任务完成的回调
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        // 将下载的文件从临时位置移动到目标位置
        let fileManager = FileManager.default
        let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let destinationURL = documentsURL.appendingPathComponent(location.lastPathComponent)
        
        do {
            try fileManager.moveItem(at: location, to: destinationURL)
            print("File downloaded to: \(destinationURL)")
        } catch {
            print("Error moving file: \(error)")
        }
    }
    
    // 处理上传任务完成的回调
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error {
            print("Task completed with error: \(error)")
        } else {
            print("Task completed successfully")
        }
    }
    
    // 处理后台任务完成后的回调
    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        DispatchQueue.main.async {
            if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
               let backgroundCompletionHandler = appDelegate.backgroundCompletionHandler {
                appDelegate.backgroundCompletionHandler = nil
                backgroundCompletionHandler()
            }
        }
    }
}

3. 在 AppDelegate 中处理后台事件

我们还需要在 AppDelegate 中处理后台任务完成后的事件,确保应用在后台任务完成后能够正确恢复。

less 复制代码
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    var backgroundCompletionHandler: (() -> Void)?
    
    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
        backgroundCompletionHandler = completionHandler
    }
}

做完这一步,我们的后台传输任务就可以正常工作了。当下载或上传任务完成时,我们会收到相应的回调通知,可以在回调中处理下载的文件或上传的结果。

如果这时 App 退到后台,我们的任务会继续进行,一直到完成。

但如果 App 被杀掉,就无法继续下载了,如果你想要让 App 能够一直在后台保持下载,还需要申请后台运行的权限,可以继续往下看。

4. 申请 App 在后台运行

为了确保下载任务在应用被挂起时不会被系统杀掉,我们需要申请一些后台模式。你可以在 Xcode 的 Capabilities 中打开 Background Modes,并勾选 Background fetch

同时,我们还可以在 AppDelegate 中使用 beginBackgroundTask 来申请更多的后台执行时间。

swift 复制代码
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    var backgroundCompletionHandler: (() -> Void)?
    var backgroundTask: UIBackgroundTaskIdentifier = .invalid
    
    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
        backgroundCompletionHandler = completionHandler
    }
    
    func applicationDidEnterBackground(_ application: UIApplication) {
        backgroundTask = application.beginBackgroundTask(withName: "DownloadTask") {
            // 当后台任务结束时调用
            application.endBackgroundTask(self.backgroundTask)
            self.backgroundTask = .invalid
        }
    }
    
    func applicationWillEnterForeground(_ application: UIApplication) {
        if backgroundTask != .invalid {
            application.endBackgroundTask(backgroundTask)
            backgroundTask = .invalid
        }
    }
}

使用示例

以下是如何使用 BackgroundSessionManager 进行后台文件下载和上传的示例:

typescript 复制代码
import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 启动文件下载
        let downloadURL = URL(string: "https://github.com/onevcat/Kingfisher/releases/download/7.12.0/Kingfisher-7.12.0.zip")!  // 一个可用的文件下载地址
        BackgroundSessionManager.shared.startDownload(url: downloadURL)
        
        // 启动文件上传
        if let fileURL = Bundle.main.url(forResource: "sample", withExtension: "txt") {
            let serverURL = URL(string: "https://example.com/upload")!
            BackgroundSessionManager.shared.startUpload(fileURL: fileURL, to: serverURL)
        }
    }
}

在这个示例中,我们启动了一个文件下载任务和一个文件上传任务。即使应用被挂起或终止,这些任务也会继续进行,并在完成后通过回调通知应用。

总结

在这篇文章中,我们介绍了如何使用 URLSession 的后台传输任务。通过创建支持后台传输的 URLSessionConfiguration,并实现相应的回调处理,我们可以确保网络传输任务即使在应用处于后台状态时也能继续进行。

这篇文章比较简单,主要是介绍了后台传输任务的基本使用方法,如果想做完善的后台传输任务,还需要考虑一些其他因素,比如:

  • 如何处理后台任务完成后的恢复

  • 如何处理后台任务的失败重试

  • 如何处理后台任务的优先级

  • 如何处理后台任务的并发数

我们还讨论了如何申请 App 在后台运行,以确保下载任务不会被终止。希望这篇文章对你有所帮助,能够在实际项目中应用这些技术来实现更稳定和可靠的文件传输。下一篇文章将继续探讨 URLSession 的其他高级用法,敬请期待!

这里每天分享一个 iOS 的新知识,快来关注我吧

本文同步自微信公众号 "iOS新知",每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!

相关推荐
丿Wayne1 小时前
iOS Token 刷新实战(Actor 避免并发重复刷新)
ios·swift·token·actor·swift并发
终端安全笔记21 小时前
iOS 27 给了租赁一个新工具,但它只认受监督的设备
android·网络·安全·ios
大熊猫侯佩1 天前
独立 App 首发完成 iPhone Duo 展开态适配
ios·swiftui·swift
光影少年1 天前
setImmediate 和 setTimeout(0) 的区别
android·前端·react.js·ios·前端框架
2501_916008891 天前
SSE 和 gRPC 流式接口如何抓包,调试 SSE 与 gRPC 流式接口
网络协议·计算机网络·网络安全·ios·adb·https·udp
HouWan2 天前
Flutter: MediaQuery.of(context) 为什么可能拖慢页面?
android·flutter·ios
SoaringHeart2 天前
Swift 进阶&封装:仿 Get 给SwiftUI 添加路由导航能力
swiftui·swift
用户38034165882972 天前
Broadcast Extension 里的 Live Activity:一次跨进程妥协
ios·音视频开发
匠测AI说2 天前
XCUITest 自动化全解:同进程白盒、XCTest 架构、自动同步机制与 iOS 端选型指南
测试工具·ios·自动化
silianpan2 天前
CAD 文档预览 UTS 插件
android·ios·harmonyos