Как объединить несколько дочерних Прогрессов в один мастер Прогресс в Swift

Я пытаюсь загрузить несколько файлов одновременно, используя NSURLSession в Swift. Я хочу объединить все состояние процесса загрузки в один, чтобы показать 100%, когда все файлы загружены. В настоящее время я получаю 100% за каждое завершение загрузки файла, но мне нужно 100%, только если все файлы загружены. Как я могу добиться этого в Swift?

Вот мой класс DownloadManager:

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, +))
    }
}

}

3 ответа

Переменные для хранения данных и расчета прогресса

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)")
}

не забудьте сбросить переменную ОжидаемыйКонтент при запуске загрузки, как это

expectedContentLength = 0

Вы можете использовать dispatchgroup 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)
 }) 
}

У меня было аналогичное требование, и вот как мне удалось исправить, у меня был список тем, и каждый 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 и добавьте каждую задачу в качестве дочерней.

Наконец, соблюдайте fractionCompleted свойство NSProgress и обновите свой индикатор прогресса.

Другие вопросы по тегам