get progress from dataTaskWithURL in swift

后端 未结 6 1038
庸人自扰
庸人自扰 2020-11-30 03:49

Is there any way to get progress from dataTaskWithURL in swift while the data is downloading?

NSURLSession.sharedSession().dataTaskWithURL(...)         


        
相关标签:
6条回答
  • 2020-11-30 04:31

    you can use this code for showing download process with progress bar with its delegate functions.

    import UIKit
    
    class ViewController: UIViewController,NSURLSessionDelegate,NSURLSessionDataDelegate{
    
        @IBOutlet weak var progress: UIProgressView!
    
        var buffer:NSMutableData = NSMutableData()
        var session:NSURLSession?
        var dataTask:NSURLSessionDataTask?
        let url = NSURL(string:"http://i.stack.imgur.com/b8zkg.png" )!
        var expectedContentLength = 0
    
    
        override func viewDidLoad() {
            super.viewDidLoad()
            progress.progress = 0.0
            let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
            let manqueue = NSOperationQueue.mainQueue()
            session = NSURLSession(configuration: configuration, delegate:self, delegateQueue: manqueue)
            dataTask = session?.dataTaskWithRequest(NSURLRequest(URL: url))
            dataTask?.resume()
    
            // Do any additional setup after loading the view, typically from a nib.
        }
        func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
    
            //here you can get full lenth of your content
            expectedContentLength = Int(response.expectedContentLength)
            println(expectedContentLength)
            completionHandler(NSURLSessionResponseDisposition.Allow)
        }
        func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
    
    
            buffer.appendData(data)
    
            let percentageDownloaded = Float(buffer.length) / Float(expectedContentLength)
            progress.progress =  percentageDownloaded
        }
        func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
            //use buffer here.Download is done
            progress.progress = 1.0   // download 100% complete
        }
    }
    
    0 讨论(0)
  • 2020-11-30 04:31

    in class declare

    class AudioPlayerViewController: UIViewController, URLSessionDelegate, URLSessionDataDelegate, URLSessionDownloadDelegate{
    
    
             var defaultSession: URLSession!
             var downloadTask: URLSessionDownloadTask!
    

    in download method

                defaultSession = Foundation.URLSession(configuration: .default, delegate: self, delegateQueue: nil)
                downloadProgress.setProgress(0.0, animated: false)
                downloadTask = defaultSession.downloadTask(with: audioUrl)
                downloadTask.Resume()
    

    And add following delegates:

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
    
        DispatchQueue.main.async {
            self.downloadProgressBar.setProgress(Float(totalBytesWritten)/Float(totalBytesExpectedToWrite), animated: true)
        }
    }
    
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
            DispatchQueue.main.async {
    
    //DOWNLOAD SUCCESSFUL AND FILE PATH WILL BE IN URL.
    
    }
    }
    
    0 讨论(0)
  • 2020-11-30 04:31

    In reference to Dharmesh's work, there have been quite a number of minor changes with URLSessionDelegate and URLSessionDataDelegate delegate methods. Here come the code for Swift 4.2 compatibility.

    import UIKit
    
    class ViewController: UIViewController, URLSessionDelegate, URLSessionDataDelegate {
        // MARK: - Variables
    
        // MARK: - IBOutlet
        @IBOutlet weak var progress: UIProgressView!
    
        // MARK: - IBAction
        @IBAction func goTapped(_ sender: UIButton) {
            let url = URL(string: "http://www.example.com/file.zip")!
            fetchFile(url: url)
        }
    
        // MARK: - Life cycle
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    
        func fetchFile(url: URL) {
            progress.progress = 0.0
            let configuration = URLSessionConfiguration.default
            let mainQueue = OperationQueue.main
            session = URLSession(configuration: configuration, delegate: self, delegateQueue: mainQueue)
            dataTask = session?.dataTask(with: URLRequest(url: url))
            dataTask?.resume()
        }
    
        var buffer: NSMutableData = NSMutableData()
        var session: URLSession?
        var dataTask: URLSessionDataTask?
        var expectedContentLength = 0
    
        func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
            buffer.append(data)
            let percentageDownloaded = Float(buffer.length) / Float(expectedContentLength)
            progress.progress =  percentageDownloaded
        }
    
        func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: (URLSession.ResponseDisposition) -> Void) {
            expectedContentLength = Int(response.expectedContentLength)
            completionHandler(URLSession.ResponseDisposition.allow)
        }
    
        func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
            progress.progress = 1.0
        }
    }
    
    0 讨论(0)
  • 2020-11-30 04:35

    You can simply observe progress property of the URLSessionDataTask object.

    Example:

    import UIKit
    
    class SomeViewController: UIViewController {
    
      private var observation: NSKeyValueObservation?
    
      deinit {
        observation?.invalidate()
      }
    
      override func viewDidLoad() {
        super.viewDidLoad()
    
        let url = URL(string: "https://source.unsplash.com/random/4000x4000")!
        let task = URLSession.shared.dataTask(with: url)
    
        observation = task.progress.observe(\.fractionCompleted) { progress, _ in
          print("progress: ", progress.fractionCompleted)
        }
    
        task.resume()
      }
    }
    

    Playground example:

    import Foundation
    import PlaygroundSupport
    
    let page = PlaygroundPage.current
    page.needsIndefiniteExecution = true
    
    let url = URL(string: "https://source.unsplash.com/random/4000x4000")!
    let task = URLSession.shared.dataTask(with: url) { _, _, _ in
      page.finishExecution()
    }
    
    // Don't forget to invalidate the observation when you don't need it anymore.
    let observation = task.progress.observe(\.fractionCompleted) { progress, _ in
      print(progress.fractionCompleted)
    }
    
    task.resume()
    
    0 讨论(0)
  • 2020-11-30 04:48

    Update for Swift4:
    Supports execution of multiple simultaneous operations.

    File: DownloadService.swift. Keeps reference to URLSession and tracks executing tasks.

    final class DownloadService: NSObject {
    
       private var session: URLSession!
       private var downloadTasks = [GenericDownloadTask]()
    
       public static let shared = DownloadService()
    
       private override init() {
          super.init()
          let configuration = URLSessionConfiguration.default
          session = URLSession(configuration: configuration,
                               delegate: self, delegateQueue: nil)
       }
    
       func download(request: URLRequest) -> DownloadTask {
          let task = session.dataTask(with: request)
          let downloadTask = GenericDownloadTask(task: task)
          downloadTasks.append(downloadTask)
          return downloadTask
       }
    }
    
    
    extension DownloadService: URLSessionDataDelegate {
    
       func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
                       completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
    
          guard let task = downloadTasks.first(where: { $0.task == dataTask }) else {
             completionHandler(.cancel)
             return
          }
          task.expectedContentLength = response.expectedContentLength
          completionHandler(.allow)
       }
    
       func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
          guard let task = downloadTasks.first(where: { $0.task == dataTask }) else {
             return
          }
          task.buffer.append(data)
          let percentageDownloaded = Double(task.buffer.count) / Double(task.expectedContentLength)
          DispatchQueue.main.async {
             task.progressHandler?(percentageDownloaded)
          }
       }
    
       func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
          guard let index = downloadTasks.index(where: { $0.task == task }) else {
             return
          }
          let task = downloadTasks.remove(at: index)
          DispatchQueue.main.async {
             if let e = error {
                task.completionHandler?(.failure(e))
             } else {
                task.completionHandler?(.success(task.buffer))
             }
          }
       }
    }
    

    File: DownloadTask.swift. Lightweight interface just to hide concrete implementation.

    protocol DownloadTask {
    
       var completionHandler: ResultType<Data>.Completion? { get set }
       var progressHandler: ((Double) -> Void)? { get set }
    
       func resume()
       func suspend()
       func cancel()
    }
    

    File: GenericDownloadTask.swift. Concrete implementation of DownloadTask interface.

    class GenericDownloadTask {
    
       var completionHandler: ResultType<Data>.Completion?
       var progressHandler: ((Double) -> Void)?
    
       private(set) var task: URLSessionDataTask
       var expectedContentLength: Int64 = 0
       var buffer = Data()
    
       init(task: URLSessionDataTask) {
          self.task = task
       }
    
       deinit {
          print("Deinit: \(task.originalRequest?.url?.absoluteString ?? "")")
       }
    
    }
    
    extension GenericDownloadTask: DownloadTask {
    
       func resume() {
          task.resume()
       }
    
       func suspend() {
          task.suspend()
       }
    
       func cancel() {
          task.cancel()
       }
    }
    

    File: ResultType.swift. Reusable type to keep result or error.

    public enum ResultType<T> {
    
       public typealias Completion = (ResultType<T>) -> Void
    
       case success(T)
       case failure(Swift.Error)
    
    }
    

    Usage: Example how to run two download tasks in parallel (macOS App):

    class ViewController: NSViewController {
    
       @IBOutlet fileprivate weak var loadImageButton1: NSButton!
       @IBOutlet fileprivate weak var loadProgressIndicator1: NSProgressIndicator!
       @IBOutlet fileprivate weak var imageView1: NSImageView!
    
       @IBOutlet fileprivate weak var loadImageButton2: NSButton!
       @IBOutlet fileprivate weak var loadProgressIndicator2: NSProgressIndicator!
       @IBOutlet fileprivate weak var imageView2: NSImageView!
    
       fileprivate var downloadTask1:  DownloadTask?
       fileprivate var downloadTask2:  DownloadTask?
    
       override func viewDidLoad() {
          super.viewDidLoad()
          loadImageButton1.target = self
          loadImageButton1.action = #selector(startDownload1(_:))
          loadImageButton2.target = self
          loadImageButton2.action = #selector(startDownload2(_:))
       }
    
    }
    
    
    extension ViewController {
    
       @objc fileprivate func startDownload1(_ button: NSButton) {
          let url = URL(string: "http://localhost:8001/?imageID=01&tilestamp=\(Date.timeIntervalSinceReferenceDate)")!
          let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
          downloadTask1 = DownloadService.shared.download(request: request)
          downloadTask1?.completionHandler = { [weak self] in
             switch $0 {
             case .failure(let error):
                print(error)
             case .success(let data):
                print("Number of bytes: \(data.count)")
                self?.imageView1.image = NSImage(data: data)
             }
             self?.downloadTask1 = nil
             self?.loadImageButton1.isEnabled = true
          }
          downloadTask1?.progressHandler = { [weak self] in
             print("Task1: \($0)")
             self?.loadProgressIndicator1.doubleValue = $0
          }
    
          loadImageButton1.isEnabled = false
          imageView1.image = nil
          loadProgressIndicator1.doubleValue = 0
          downloadTask1?.resume()
       }
    
       @objc fileprivate func startDownload2(_ button: NSButton) {
          let url = URL(string: "http://localhost:8002/?imageID=02&tilestamp=\(Date.timeIntervalSinceReferenceDate)")!
          let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
          downloadTask2 = DownloadService.shared.download(request: request)
          downloadTask2?.completionHandler = { [weak self] in
             switch $0 {
             case .failure(let error):
                print(error)
             case .success(let data):
                print("Number of bytes: \(data.count)")
                self?.imageView2.image = NSImage(data: data)
             }
             self?.downloadTask2 = nil
             self?.loadImageButton2.isEnabled = true
          }
          downloadTask2?.progressHandler = { [weak self] in
             print("Task2: \($0)")
             self?.loadProgressIndicator2.doubleValue = $0
          }
    
          loadImageButton2.isEnabled = false
          imageView2.image = nil
          loadProgressIndicator2.doubleValue = 0
          downloadTask2?.resume()
       }
    }
    

    Bonus 1. File StartPHPWebServer.command. Example script to run 2 Build-in PHP servers to simulate simultaneous downloads.

    #!/bin/bash
    
    AWLScriptDirPath=$(cd "$(dirname "$0")"; pwd)
    cd "$AWLScriptDirPath"
    
    php -S localhost:8001 &
    php -S localhost:8002 &
    
    ps -afx | grep php
    echo "Press ENTER to exit."
    read
    killall php
    

    Bonus 2. File index.php. Example PHP script to implement slow download.

    <?php
    
    $imageID = $_REQUEST["imageID"];
    $local_file = "Image-$imageID.jpg";
    
    $download_rate = 20.5; // set the download rate limit (=> 20,5 kb/s)
    if (file_exists($local_file) && is_file($local_file)) {
        header('Cache-control: private');
        header('Content-Type: image/jpeg');
        header('Content-Length: '.filesize($local_file));
    
        flush();
        $file = fopen($local_file, "r");
        while(!feof($file)) {
            // send the current file part to the browser
            print fread($file, round($download_rate * 1024));
            flush(); // flush the content to the browser
            usleep(0.25 * 1000000);
        }
        fclose($file);}
    else {
        die('Error: The file '.$local_file.' does not exist!');
    }
    
    ?>
    

    Misc: Contents of directory which simulates 2 PHP servers.

    Image-01.jpg
    Image-02.jpg
    StartPHPWebServer.command
    index.php
    
    0 讨论(0)
  • 2020-11-30 04:49

    for the data been download you need to set the NSURLSessionDownloadDelegate and to implement URLSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)

    There is a nice tutorial about it here but is in object-c.

    0 讨论(0)
提交回复
热议问题