vez todas seleccionar para nube los las fotos descargar cómo archivos aplicacion ios swift nsurlsession

ios - para - seleccionar todas las fotos en icloud



Cómo descargar múltiples archivos secuencialmente usando la función de descarga de NSURLSession en Swift (4)

Tengo una aplicación que tiene que descargar varios archivos grandes. Quiero que descargue cada archivo uno por uno secuencialmente en lugar de al mismo tiempo. Cuando se ejecuta simultáneamente, la aplicación se sobrecarga y se bloquea.

Asi que. Estoy tratando de envolver un downloadTaskWithURL dentro de un NSBlockOperation y luego configurar maxConcurrentOperationCount = 1 en la cola. Escribí este código a continuación, pero no funcionó ya que ambos archivos se descargan simultáneamente.

import UIKit class ViewController: UIViewController, NSURLSessionDelegate, NSURLSessionDownloadDelegate { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. processURLs() } func download(url: NSURL){ let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration() let session = NSURLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil) let downloadTask = session.downloadTaskWithURL(url) downloadTask.resume() } func processURLs(){ //setup queue and set max conncurrent to 1 var queue = NSOperationQueue() queue.name = "Download queue" queue.maxConcurrentOperationCount = 1 let url = NSURL(string: "http://azspeastus.blob.core.windows.net/azurespeed/100MB.bin?sv=2014-02-14&sr=b&sig=%2FZNzdvvzwYO%2BQUbrLBQTalz%2F8zByvrUWD%2BDfLmkpZuQ%3D&se=2015-09-01T01%3A48%3A51Z&sp=r") let url2 = NSURL(string: "http://azspwestus.blob.core.windows.net/azurespeed/100MB.bin?sv=2014-02-14&sr=b&sig=ufnzd4x9h1FKmLsODfnbiszXd4EyMDUJgWhj48QfQ9A%3D&se=2015-09-01T01%3A48%3A51Z&sp=r") let urls = [url, url2] for url in urls { let operation = NSBlockOperation { () -> Void in println("starting download") self.download(url!) } queue.addOperation(operation) } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) { //code } func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) { // } func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { var progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite) println(progress) } }

¿Cómo puedo escribir esto correctamente para lograr mi objetivo de solo descargar un archivo a la vez?


Aquí es bastante minimalista y de enfoque puramente veloz. Sin NSOperationQueue (), solo hice Set-observer

import Foundation class DownloadManager { var delegate: HavingWebView? var gotFirstAndEnough = true var finalURL: NSURL?{ didSet{ if finalURL != nil { if let s = self.contentOfURL{ self.delegate?.webView.loadHTMLString(s, baseURL: nil) } } } } var lastRequestBeginning: NSDate? var myLinks = [String](){ didSet{ self.handledLink = self.myLinks.count } } var contentOfURL: String? var handledLink = 0 { didSet{ if handledLink == 0 { self.finalURL = nil print("🔴🔶🔴🔶🔶🔴🔶🔴🔶🔴🔶🔴") } else { if self.finalURL == nil { if let nextURL = NSURL(string: self.myLinks[self.handledLink-1]) { self.loadAsync(nextURL) } } } } } func loadAsync(url: NSURL) { let sessionConfig = NSURLSessionConfiguration.ephemeralSessionConfiguration() let session = NSURLSession(configuration: sessionConfig, delegate: nil, delegateQueue: NSOperationQueue.mainQueue()) let request = NSMutableURLRequest(URL: url, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringCacheData, timeoutInterval: 15.0) request.HTTPMethod = "GET" print("🚀") self.lastRequestBeginning = NSDate() print("Requet began: /(self.lastRequestBeginning )") let task = session.dataTaskWithRequest(request, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in if (error == nil) { if let response = response as? NSHTTPURLResponse { print("/(response)") if response.statusCode == 200 { if let content = String(data: data!, encoding: NSUTF8StringEncoding) { self.contentOfURL = content } self.finalURL = url } } } else { print("Failure: /(error!.localizedDescription)"); } let elapsed = NSDate().timeIntervalSinceDate(self.lastRequestBeginning!) print("trying /(url) takes /(elapsed)") print("🏁 Request finished") print("____________________________________________") self.handledLink -= 1 }) task.resume() } }

En ViewController:

protocol HavingWebView { var webView: UIWebView! {get set} } class ViewController: UIViewController, HavingWebView { @IBOutlet weak var webView: UIWebView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let dm = DownloadManager() dm.delegate = self dm.myLinks = ["https://medium.com/the-mission/consider-the-present-and-future-value-of-your-decisions-b20fb72f5e#.a12uiiz11", "https://medium.com/@prianka.kariat/ios-10-notifications-with-attachments-and-much-more-169a7405ddaf#.svymi6230", "https://myerotica.com/jingle-bell-fuck-the-twins-5a48782bf5f1#.mjqz821yo", "https://blog.medium.com/39-reasons-we-wont-soon-forget-2016-154ac95683af#.cmb37i58b", "https://backchannel.com/in-2017-your-coworkers-will-live-everywhere-ae14979b5255#.wmi6hxk9p"] } }


La versión de Objective-C es:

[operation2 addDependency:operation1]


Más de un código en la situación de fondo. Puedo aprender por variable global utilizada y NSTimer. Puedes intentarlo también.

Defina la variable global ''indexDownloaded''.

import UIKit import Foundation private let _sharedUpdateStatus = UpdateStatus() class UpdateStatus : NSObject { // MARK: - SHARED INSTANCE class var shared : UpdateStatus { return _sharedUpdateStatus } var indexDownloaded = 0 }

Este código se agrega en la clase DownloadOperation.

print("⬇️" + URL.lastPathComponent! + " downloaded") UpdateStatus.shared.indexDownloaded += 1 print(String(UpdateStatus.shared.indexDownloaded) + "//" + String(UpdateStatus.shared.count))

Esta función en su viewController.

func startTimeAction () { let urlStrings = [ "http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/s72-55482.jpg", "http://spaceflight.nasa.gov/gallery/images/apollo/apollo10/hires/as10-34-5162.jpg", "http://spaceflight.nasa.gov/gallery/images/apollo-soyuz/apollo-soyuz/hires/s75-33375.jpg", "http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-134-20380.jpg", "http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-140-21497.jpg", "http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-148-22727.jpg" ] let urls = urlStrings.flatMap { URL(string: $0) } for url in urls { queue.addOperation(DownloadOperation(session: session, url: url)) } UpdateStatus.shared.count = urls.count progressView.setProgress(0.0, animated: false) timer.invalidate() timer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: #selector(timeAction), userInfo: nil, repeats: true) } func timeAction() { if UpdateStatus.shared.count != 0 { let set: Float = Float(UpdateStatus.shared.indexDownloaded) / Float(UpdateStatus.shared.count) progressView.setProgress(set, animated: true) }

De esta manera, al actualizar la vista de progreso se verá el número de descargas cada vez que se ejecuta el temporizador.


Su código no funcionará porque URLSessionDownloadTask ejecuta de forma asíncrona. Por lo tanto, BlockOperation completa antes de que se realice la descarga y, por lo tanto, mientras las operaciones se BlockOperation de forma secuencial, las tareas de descarga continuarán de forma asíncrona y en paralelo.

Para solucionar esto, puede ajustar las solicitudes en Operation subclase de Operation asíncrona. Consulte Configuración de operaciones para ejecución concurrente en la Guía de programación de concurrencia para obtener más información.

Pero antes de ilustrar cómo hacer esto en su situación (la URLSession basada en URLSession ), primero permítame mostrarle la solución más sencilla al usar la representación del controlador de finalización. Más adelante nos basaremos en esto para su pregunta más complicada. Entonces, en Swift 3 y más tarde:

class DownloadOperation : AsynchronousOperation { var task: URLSessionTask! init(session: URLSession, url: URL) { super.init() task = session.downloadTask(with: url) { temporaryURL, response, error in defer { self.finish() } guard let temporaryURL = temporaryURL, error == nil else { print(error ?? "Unknown error") return } do { let manager = FileManager.default let destinationURL = try manager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) .appendingPathComponent(url.lastPathComponent) try? manager.removeItem(at: destinationURL) // remove the old one, if any try manager.moveItem(at: temporaryURL, to: destinationURL) // move new one there } catch let moveError { print("/(moveError)") } } } override func cancel() { task.cancel() super.cancel() } override func main() { task.resume() } }

Dónde

/// Asynchronous operation base class /// /// This is abstract to class performs all of the necessary KVN of `isFinished` and /// `isExecuting` for a concurrent `Operation` subclass. You can subclass this and /// implement asynchronous operations. All you must do is: /// /// - override `main()` with the tasks that initiate the asynchronous task; /// /// - call `completeOperation()` function when the asynchronous task is done; /// /// - optionally, periodically check `self.cancelled` status, performing any clean-up /// necessary and then ensuring that `finish()` is called; or /// override `cancel` method, calling `super.cancel()` and then cleaning-up /// and ensuring `finish()` is called. class AsynchronousOperation: Operation { /// State for this operation. @objc private enum OperationState: Int { case ready case executing case finished } /// Concurrent queue for synchronizing access to `state`. private let stateQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".rw.state", attributes: .concurrent) /// Private backing stored property for `state`. private var rawState: OperationState = .ready /// The state of the operation @objc private dynamic var state: OperationState { get { return stateQueue.sync { rawState } } set { stateQueue.sync(flags: .barrier) { rawState = newValue } } } // MARK: - Various `Operation` properties open override var isReady: Bool { return state == .ready && super.isReady } public final override var isExecuting: Bool { return state == .executing } public final override var isFinished: Bool { return state == .finished } // KVN for dependent properties open override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> { if ["isReady", "isFinished", "isExecuting"].contains(key) { return [#keyPath(state)] } return super.keyPathsForValuesAffectingValue(forKey: key) } // Start public final override func start() { if isCancelled { finish() return } state = .executing main() } /// Subclasses must implement this to perform their work and they must not call `super`. The default implementation of this function throws an exception. open override func main() { fatalError("Subclasses must implement `main`.") } /// Call this function to finish an operation that is currently executing public final func finish() { if isExecuting { state = .finished } } }

Entonces puedes hacer:

for url in urls { queue.addOperation(DownloadOperation(session: session, url: url)) }

Así que esa es una manera muy fácil de envolver las URLSession asíncronas de URLSession / NSURLSession en la subclase de Operation asíncrona / NSOperation . Más generalmente, este es un patrón útil, que usa AsynchronousOperation para envolver alguna tarea asíncrona en un objeto Operation / NSOperation .

Desafortunadamente, en su pregunta, usted quería usar URLSession / NSURLSession basada en NSURLSession para poder monitorear el progreso de las descargas. Esto es más complicado.

Esto se debe a que los métodos del delegado NSURLSession "tarea completa" se NSURLSession en el delegado del objeto de la sesión. Esta es una característica de diseño exasperante de NSURLSession (pero Apple lo hizo para simplificar las sesiones en segundo plano, lo cual no es relevante aquí, pero estamos atrapados en esa limitación de diseño).

Pero tenemos que completar asíncronamente las operaciones a medida que terminan las tareas. Por lo tanto, necesitamos alguna forma para que la sesión se resuelva con la operación para completar cuando se llama didCompleteWithError . Ahora puede hacer que cada operación tenga su propio objeto NSURLSession , pero resulta que esto es bastante ineficiente.

Entonces, para manejar eso, mantengo un diccionario, codificado por el identificador de tareas de la tarea, que identifica la operación apropiada. De esa manera, cuando finalice la descarga, puede "completar" la operación asíncrona correcta. Así:

/// Manager of asynchronous download `Operation` objects class DownloadManager: NSObject { /// Dictionary of operations, keyed by the `taskIdentifier` of the `URLSessionTask` fileprivate var operations = [Int: DownloadOperation]() /// Serial OperationQueue for downloads private let queue: OperationQueue = { let _queue = OperationQueue() _queue.name = "download" _queue.maxConcurrentOperationCount = 1 // I''d usually use values like 3 or 4 for performance reasons, but OP asked about downloading one at a time return _queue }() /// Delegate-based `URLSession` for DownloadManager lazy var session: URLSession = { let configuration = URLSessionConfiguration.default return URLSession(configuration: configuration, delegate: self, delegateQueue: nil) }() /// Add download /// /// - parameter URL: The URL of the file to be downloaded /// /// - returns: The DownloadOperation of the operation that was queued @discardableResult func queueDownload(_ url: URL) -> DownloadOperation { let operation = DownloadOperation(session: session, url: url) operations[operation.task.taskIdentifier] = operation queue.addOperation(operation) return operation } /// Cancel all queued operations func cancelAll() { queue.cancelAllOperations() } } // MARK: URLSessionDownloadDelegate methods extension DownloadManager: URLSessionDownloadDelegate { func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { operations[downloadTask.taskIdentifier]?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location) } func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { operations[downloadTask.taskIdentifier]?.urlSession(session, downloadTask: downloadTask, didWriteData: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite) } } // MARK: URLSessionTaskDelegate methods extension DownloadManager: URLSessionTaskDelegate { func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { let key = task.taskIdentifier operations[key]?.urlSession(session, task: task, didCompleteWithError: error) operations.removeValue(forKey: key) } } /// Asynchronous Operation subclass for downloading class DownloadOperation : AsynchronousOperation { let task: URLSessionTask init(session: URLSession, url: URL) { task = session.downloadTask(with: url) super.init() } override func cancel() { task.cancel() super.cancel() } override func main() { task.resume() } } // MARK: NSURLSessionDownloadDelegate methods extension DownloadOperation: URLSessionDownloadDelegate { func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { do { let manager = FileManager.default let destinationURL = try manager.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) .appendingPathComponent(downloadTask.originalRequest!.url!.lastPathComponent) try? manager.removeItem(at: destinationURL) try manager.moveItem(at: location, to: destinationURL) } catch { print(error) } } func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite) print("/(downloadTask.originalRequest!.url!.absoluteString) /(progress)") } } // MARK: URLSessionTaskDelegate methods extension DownloadOperation: URLSessionTaskDelegate { func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { defer { finish() } if let error = error { print(error) return } // do whatever you want upon success } }

Y luego usarlo así:

let downloadManager = DownloadManager() override func viewDidLoad() { super.viewDidLoad() let urlStrings = [ "http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/s72-55482.jpg", "http://spaceflight.nasa.gov/gallery/images/apollo/apollo10/hires/as10-34-5162.jpg", "http://spaceflight.nasa.gov/gallery/images/apollo-soyuz/apollo-soyuz/hires/s75-33375.jpg", "http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-134-20380.jpg", "http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-140-21497.jpg", "http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-148-22727.jpg" ] let urls = urlStrings.compactMap { URL(string: $0) } let completion = BlockOperation { print("all done") } for url in urls { let operation = downloadManager.queueDownload(url) completion.addDependency(operation) } OperationQueue.main.addOperation(completion) }

Ver el historial de revisiones para la implementación de Swift 2.