新需求有一个后台下载的功能,由于之前没有涉及过相关内容,于是马上开始搜索 Moya 怎么实现后台下载,一搜才发现,最新的 Moya 已经不支持后台下载了,那就只有用原生的 URLSession 来做了。
先来看一下整个后台下载的流程:
- 打开应用程序,启动后台下载任务;
- 进入后台,下载在后台继续;
- 下载完成后,会调用
UIApplicationDelegate
的 application(_ application:handleEventsForBackgroundURLSession:completionHandler:)
方法,此处意思就是如果后台下载完成后,提供一个闭包,可以调用闭包方法告诉系统我们的应用可以被suspend了;
- 接着会调用
URLSessionDelegate
的 urlSessionDidFinishEvents(forBackgroundURLSession:
方法,在这里我们获取上一步中的闭包进行调用。
好了,接下来看详细的代码。
1 2 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
实现下面的方法:
1 2 3 4 5 6 7
| var backgroundCompletionHandler: (() -> Void)?
...
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { backgroundCompletionHandler = completionHandler }
|
最后开启下载:
1 2 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