新需求有一个后台下载的功能,由于之前没有涉及过相关内容,于是马上开始搜索 Moya 怎么实现后台下载,一搜才发现,最新的 Moya 已经不支持后台下载了,那就只有用原生的 URLSession 来做了。
先来看一下整个后台下载的流程:
- 打开应用程序,启动后台下载任务;
- 进入后台,下载在后台继续;
- 下载完成后,会调用 UIApplicationDelegate的application(_ application:handleEventsForBackgroundURLSession:completionHandler:)方法,此处意思就是如果后台下载完成后,提供一个闭包,可以调用闭包方法告诉系统我们的应用可以被suspend了;
- 接着会调用 URLSessionDelegate的urlSessionDidFinishEvents(forBackgroundURLSession:方法,在这里我们获取上一步中的闭包进行调用。
好了,接下来看详细的代码。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 
 | class BackgroundDownloadManager: NSObject {
 private var progressHandler: ((Double) -> Void)?
 
 
 private var completionHandler: ((String) -> Void)?
 
 lazy var session: URLSession = {
 let config = URLSessionConfiguration.background(withIdentifier: "com.demo.download")
 let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
 return session
 }()
 
 func download(urlString: String, progress: ((Double) -> Void)? = nil, completion: ((String) -> Void)? = nil) {
 self.progressHandler = progress
 self.completionHandler = completion
 
 let url = URL(string: urlString)!
 let urlRequest = URLRequest(url: url)
 let task = session.downloadTask(with: urlRequest)
 task.resume()
 }
 }
 
 
 
 extension BackgroundDownloadManager: URLSessionDelegate {
 func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
 DispatchQueue.main.async {
 if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
 let completionHandler = appDelegate.backgroundCompletionHandler {
 completionHandler()
 }
 }
 }
 }
 
 
 
 extension BackgroundDownloadManager: URLSessionDownloadDelegate {
 func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
 let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
 progressHandler?(progress)
 }
 
 func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
 progressHandler?(1)
 let fileName = dateToString(date: Date(), format: "yyyyMMddHHmmss") + ".tmp"
 let newLocation = NSHomeDirectory() + "/Documents/" + fileName
 do {
 try FileManager.default.moveItem(atPath: location.path, toPath: newLocation)
 } catch {
 print("Move item failed: \(error)")
 }
 completionHandler?(newLocation)
 }
 
 private func dateToString(date: Date, format: String) -> String {
 let dateFormatter = DateFormatter()
 dateFormatter.dateFormat = format
 return dateFormatter.string(from: date)
 }
 }
 
 | 
然后在 AppDelegate 实现下面的方法:
| 12
 3
 4
 5
 6
 7
 
 | var backgroundCompletionHandler: (() -> Void)?
 ...
 
 func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
 backgroundCompletionHandler = completionHandler
 }
 
 | 
最后开启下载:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | let downloadManager = BackgroundDownloadManager()
 ...
 
 private func startBackgroundDownload() {
 let urlString = "your file url"
 downloadManager.download(urlString: urlString) { progress in
 print("下载进度: \(progress)")
 } completion: { location in
 print("文件已保存到: \(location)")
 }
 }
 
 | 
到这里,一个完整的后台下载就结束了。
参考资料
Downloading Files in the Background