如何在 Swift 中将多个子进度聚合到一个主进度中



我正在尝试使用 Swift 中的 NSURLSession 同时下载多个文件。我想将所有下载进度状态合并为一个,以便在下载所有文件时显示 100%。目前,每次文件下载完成我都会获得 100%,但只有在下载所有文件时才需要 100%。如何在 Swift 中实现这一点?

这是我的下载管理器类:

class DownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
static var shared = DownloadManager()
var task1Progress = 0.00
var task2Progress = 0.00
typealias ProgressHandler = (Float) -> ()
var onProgress : ProgressHandler? {
didSet {
if onProgress != nil {
let _ = activate()
}
}
}
override private init() {
super.init()
}
func activate() -> URLSession {
let config = URLSessionConfiguration.background(withIdentifier: "(Bundle.main.bundleIdentifier!).background")
// Warning: If an URLSession still exists from a previous download, it doesn't create a new URLSession object but returns the existing one with the old delegate object attached!
return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
}
func calculateProgress(session : URLSession, completionHandler : @escaping (Float) -> ()) {
session.getTasksWithCompletionHandler { (tasks, uploads, downloads) in
let progress = downloads.map({ (task) -> Float in
if task.countOfBytesExpectedToReceive > 0 {
return Float(task.countOfBytesReceived) / Float(task.countOfBytesExpectedToReceive)
} else {
return 0.0
}
})
completionHandler(progress.reduce(0.0, +))
}
}

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if totalBytesExpectedToWrite > 0 {
if let onProgress = onProgress {
calculateProgress(session: session, completionHandler: onProgress)
}
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
debugPrint("Download Progress (downloadTask) (progress)")
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
debugPrint("Download finished: (location)")
try? FileManager.default.removeItem(at: location)
}

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
debugPrint("Task completed: (task), error: (String(describing: error))")
}
private func calculateProgress(session : URLSession, completionHandler : @escaping (Float) -> ()) {
session.getTasksWithCompletionHandler { (tasks, uploads, downloads) in
let progress = downloads.map({ (task) -> Float in
if task.countOfBytesExpectedToReceive > 0 {
if (task.taskIdentifier ==  1) {
self.task1Progress = Double(Float(task.countOfBytesReceived) / Float(task.countOfBytesExpectedToReceive))
} else if (task.taskIdentifier ==  2){
self.task2Progress = Double(Float(task.countOfBytesReceived) / Float(task.countOfBytesExpectedToReceive))
}
print("pro1 = (self.task1Progress) pro2 = (self.task2Progress)")
if(self.task1Progress>0.0000 && self.task2Progress>0.000) {
return Float(min(self.task1Progress  ,self.task2Progress))
}
return Float(max(self.task1Progress  ,self.task2Progress))
} else {
return 0.0
}
})
completionHandler(progress.reduce(0.0, +))
}
}

}

您可以使用调度组 tp 实现所需的行为。

要下载文件,您将调用downloadManager的共享方法。在 dispatchGroup 上调度下载操作,在通知中,您将在下载所有文件时收到回调。

请在下面找到示例:

func dispatchGroupUsage (completion: CallBack) {
let backgroundQ = DispatchQueue.global(attributes: .qosDefault)
let group = DispatchGroup()
for number in numberOfFilesToBeDownloaded {
group.enter()
backgroundQ.async(group: group,  execute: {  
// call download manager's method to download files
group.leave()
})
}
group.notify(queue: DispatchQueue.main, execute: {
print("All Done"); completion(result: fill)
}) 
}

用于存储数据和计算进度的变量

var progress: Float = 0
var expectedContentLength: Int64 = 0
var allData: Data = Data()

创建默认会话:

let defaultConfiguration = URLSessionConfiguration.default
let defaultSession = URLSession(configuration: defaultConfiguration, delegate: self, delegateQueue: nil

从 URL 下载数据,您可以根据需要多次调用此方法

func downlod(from url: URL, session: URLSession) {
let dataTask = session.dataTask(with: url)
dataTask.resume();
}

并且您需要实现以下委托

URLSessionDelegate, URLSessionDataDelegate 
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Swift.Void) {
progress = 0
expectedContentLength += response.expectedContentLength
completionHandler(.allow)
}

public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
allData.append(data)
progress = Float(allData.count) / Float(expectedContentLength)
print("progress - (progress)")
}

不要忘记在开始下载时重置预期的 ContentLength 变量,如下所示

expectedContentLength = 0

我有一个类似的要求,这就是我设法解决的方式,对我来说,我有一个主题列表,每个topic都有章节和每个chapter was a file.因此,当下载主题时,我必须下载其章节。所以这里有一个问题,假设一个主题有15个文件(我从元数据中获取数量),就会有15 download objects and 15 download taskes,每当每个任务触发进度时,我都会在下面的函数中处理它。 这个想法很简单,每个文件从0.0 to 1.0开始到结束,所以当所有 15 个文件完成时,所有进度的总和将是 15.0,这意味着所有下载在the sum of progress == total number of files时完成

func handleProgress(percentage:Float){
// Array of files
let totalFileCount = downloadData.count
totalPercentageProgress += percentage
DispatchQueue.main.async {
self.downloadProgressView.progress = self.totalPercentageProgress / Float(totalFileCount)
}
}
-----
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64){
if totalBytesExpectedToWrite > 0 {
let progressPercentage = Float(totalBytesWritten)/Float(totalBytesExpectedToWrite)
let progressHandler = getProgressHandlerForTask(identifier: downloadTask.taskIdentifier)
print(progressPercentage)
delegate?.handleProgress(progressPercentage)
}
}

只需使用 NSProgress。它使您可以完全按照自己的方式进行操作。

每个NSURLSessionTask都有一个进度属性(如果您的目标是较旧的操作系统,则可以自己创建一个)。

然后,只需创建一个父 NSProgress 实例,并将每个任务进度添加为子任务。

最后,观察 NSProgress 的fractionCompleted属性并更新进度指示器。

最新更新