簡體   English   中英

如何在 Swift 中使用 NSURLSession downloadTask 順序下載多個文件

[英]How To Download Multiple Files Sequentially using NSURLSession downloadTask in Swift

我有一個必須下載多個大文件的應用程序。 我希望它按順序而不是同時下載每個文件。 當它同時運行時,應用程序會過載並崩潰。

所以。 我試圖將 downloadTaskWithURL 包裝在 NSBlockOperation 中,然后在隊列上設置 maxConcurrentOperationCount = 1。 我在下面寫了這段代碼,但它不起作用,因為兩個文件都是同時下載的。

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

}

如何正確編寫它以實現我一次只下載一個文件的目標。

您的代碼將無法運行,因為URLSessionDownloadTask異步運行。 因此BlockOperation在下載完成之前完成,因此當操作順序觸發時,下載任務將異步並行地繼續。

雖然可以考慮一些變通方法(例如,在前一個請求完成后啟動一個請求的遞歸模式、后台線程上的非零信號量模式等),但優雅的解決方案是經過驗證的異步框架之一。 從歷史上看,如果您想控制一系列異步任務的並發程度,我們會使用異步Operation子類。 如今,在 iOS 13 及更高版本中,我們可能會考慮Combine (還有其他第三方異步編程框架,但我會限制自己使用 Apple 提供的方法。)


手術

為了解決這個問題,您可以將請求包裝在異步Operation子類中。 有關更多信息,請參閱並發編程指南中的為並發執行配置操作

但在我說明如何在您的情況下(基於委托的URLSession )執行此操作之前,讓我首先向您展示使用完成處理程序呈現時的更簡單的解決方案。 我們稍后會在此基礎上解決您更復雜的問題。 因此,在 Swift 3 及更高版本中:

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 httpResponse = response as? HTTPURLResponse,
                200..<300 ~= httpResponse.statusCode
            else {
                // handle invalid return codes however you'd like
                return
            }

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

在哪里

/// Asynchronous operation base class
///
/// This is abstract to class emits all of the necessary KVO notifications 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 }
    
    // KVO 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 !isFinished { state = .finished }
    }
}

然后你可以這樣做:

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

所以這是在異步Operation / NSOperation子類中包裝異步URLSession / NSURLSession請求的一種非常簡單的方法。 更一般地說,這是一個有用的模式,使用AsynchronousOperation將一些異步任務包裝在Operation / NSOperation對象中。

不幸的是,在您的問題中,您想使用基於委托的URLSession / NSURLSession以便您可以監視下載進度。 這個比較復雜。

這是因為在會話對象的委托中調用了“任務完成” NSURLSession委托方法。 這是NSURLSession令人惱火的設計功能(但 Apple 這樣做是為了簡化后台會話,這與此處無關,但我們NSURLSession該設計限制的困擾)。

但是我們必須在任務完成時異步完成操作。 因此,我們需要某種方式讓會話確定調用didCompleteWithError時要完成的操作。 現在你可以讓每個操作都有自己的NSURLSession對象,但事實證明這是非常低效的。

所以,為了解決這個問題,我維護了一個字典,由任務的taskIdentifier ,它標識了適當的操作。 這樣,當下載完成時,您可以“完成”正確的異步操作。 因此:

/// 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) {
        guard
            let httpResponse = downloadTask.response as? HTTPURLResponse,
            200..<300 ~= httpResponse.statusCode
        else {
            // handle invalid return codes however you'd like
            return
        }

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

然后像這樣使用它:

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

查看 Swift 2 實現的修訂歷史


結合

對於Combine ,想法是為URLSessionDownloadTask創建一個Publisher 然后你可以做這樣的事情:

var downloadRequests: AnyCancellable?

/// Download a series of assets

func downloadAssets() {
    downloadRequests = downloadsPublisher(for: urls, maxConcurrent: 1).sink { completion in
        switch completion {
        case .finished:
            print("done")

        case .failure(let error):
            print("failed", error)
        }
    } receiveValue: { destinationUrl in
        print(destinationUrl)
    }
}

/// Publisher for single download
///
/// Copy downloaded resource to caches folder.
///
/// - Parameter url: `URL` being downloaded.
/// - Returns: Publisher for the URL with final destination of the downloaded asset.

func downloadPublisher(for url: URL) -> AnyPublisher<URL, Error> {
    URLSession.shared.downloadTaskPublisher(for: url)
        .tryCompactMap {
            let destination = try FileManager.default
                .url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
                .appendingPathComponent(url.lastPathComponent)
            try FileManager.default.moveItem(at: $0.location, to: destination)
            return destination
        }
        .receive(on: RunLoop.main)
        .eraseToAnyPublisher()
}

/// Publisher for a series of downloads
///
/// This downloads not more than `maxConcurrent` assets at a given time.
///
/// - Parameters:
///   - urls: Array of `URL`s of assets to be downloaded.
///   - maxConcurrent: The maximum number of downloads to run at any given time (default 4).
/// - Returns: Publisher for the URLs with final destination of the downloaded assets.

func downloadsPublisher(for urls: [URL], maxConcurrent: Int = 4) -> AnyPublisher<URL, Error> {
    Publishers.Sequence(sequence: urls.map { downloadPublisher(for: $0) })
        .flatMap(maxPublishers: .max(maxConcurrent)) { $0 }
        .eraseToAnyPublisher()
}

現在,不幸的是,Apple 提供了一個DataTaskPublisher (它將完整資產加載到內存中,這對於大型資產來說是不可接受的解決方案),但是可以參考他們的源代碼並對其進行調整以創建一個DownloadTaskPublisher

//  DownloadTaskPublisher.swift
//
//  Created by Robert Ryan on 9/28/20.
//
//  Adapted from Apple's `DataTaskPublisher` at:
//  https://github.com/apple/swift/blob/88b093e9d77d6201935a2c2fb13f27d961836777/stdlib/public/Darwin/Foundation/Publishers%2BURLSession.swift

import Foundation
import Combine

// MARK: Download Tasks

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension URLSession {
    /// Returns a publisher that wraps a URL session download task for a given URL.
    ///
    /// The publisher publishes temporary when the task completes, or terminates if the task fails with an error.
    ///
    /// - Parameter url: The URL for which to create a download task.
    /// - Returns: A publisher that wraps a download task for the URL.

    public func downloadTaskPublisher(for url: URL) -> DownloadTaskPublisher {
        let request = URLRequest(url: url)
        return DownloadTaskPublisher(request: request, session: self)
    }

    /// Returns a publisher that wraps a URL session download task for a given URL request.
    ///
    /// The publisher publishes download when the task completes, or terminates if the task fails with an error.
    ///
    /// - Parameter request: The URL request for which to create a download task.
    /// - Returns: A publisher that wraps a download task for the URL request.

    public func downloadTaskPublisher(for request: URLRequest) -> DownloadTaskPublisher {
        return DownloadTaskPublisher(request: request, session: self)
    }

    public struct DownloadTaskPublisher: Publisher {
        public typealias Output = (location: URL, response: URLResponse)
        public typealias Failure = URLError

        public let request: URLRequest
        public let session: URLSession

        public init(request: URLRequest, session: URLSession) {
            self.request = request
            self.session = session
        }

        public func receive<S: Subscriber>(subscriber: S) where Failure == S.Failure, Output == S.Input {
            subscriber.receive(subscription: Inner(self, subscriber))
        }

        private typealias Parent = DownloadTaskPublisher
        private final class Inner<Downstream: Subscriber>: Subscription, CustomStringConvertible, CustomReflectable, CustomPlaygroundDisplayConvertible
        where
            Downstream.Input == Parent.Output,
            Downstream.Failure == Parent.Failure
        {
            typealias Input = Downstream.Input
            typealias Failure = Downstream.Failure

            private let lock: NSLocking
            private var parent: Parent?               // GuardedBy(lock)
            private var downstream: Downstream?       // GuardedBy(lock)
            private var demand: Subscribers.Demand    // GuardedBy(lock)
            private var task: URLSessionDownloadTask! // GuardedBy(lock)
            var description: String { return "DownloadTaskPublisher" }
            var customMirror: Mirror {
                lock.lock()
                defer { lock.unlock() }
                return Mirror(self, children: [
                    "task": task as Any,
                    "downstream": downstream as Any,
                    "parent": parent as Any,
                    "demand": demand,
                ])
            }
            var playgroundDescription: Any { return description }

            init(_ parent: Parent, _ downstream: Downstream) {
                self.lock = NSLock()
                self.parent = parent
                self.downstream = downstream
                self.demand = .max(0)
            }

            // MARK: - Upward Signals
            func request(_ d: Subscribers.Demand) {
                precondition(d > 0, "Invalid request of zero demand")

                lock.lock()
                guard let p = parent else {
                    // We've already been cancelled so bail
                    lock.unlock()
                    return
                }

                // Avoid issues around `self` before init by setting up only once here
                if self.task == nil {
                    let task = p.session.downloadTask(
                        with: p.request,
                        completionHandler: handleResponse(location:response:error:)
                    )
                    self.task = task
                }

                self.demand += d
                let task = self.task!
                lock.unlock()

                task.resume()
            }

            private func handleResponse(location: URL?, response: URLResponse?, error: Error?) {
                lock.lock()
                guard demand > 0,
                      parent != nil,
                      let ds = downstream
                else {
                    lock.unlock()
                    return
                }

                parent = nil
                downstream = nil

                // We clear demand since this is a single shot shape
                demand = .max(0)
                task = nil
                lock.unlock()

                if let location = location, let response = response, error == nil {
                    _ = ds.receive((location, response))
                    ds.receive(completion: .finished)
                } else {
                    let urlError = error as? URLError ?? URLError(.unknown)
                    ds.receive(completion: .failure(urlError))
                }
            }

            func cancel() {
                lock.lock()
                guard parent != nil else {
                    lock.unlock()
                    return
                }
                parent = nil
                downstream = nil
                demand = .max(0)
                let task = self.task
                self.task = nil
                lock.unlock()
                task?.cancel()
            }
        }
    }
}

現在,不幸的是,這不是使用URLSession委托模式,而是使用完成處理程序再現。 但是可以想象,它可以適應委托模式。

此外,這將在失敗時停止下載。 如果你不希望它僅僅因為一個失敗而停止,你可以想象將它定義為Never失敗,而是用nil替換replaceError

/// Publisher for single download
///
/// Copy downloaded resource to caches folder.
///
/// - Parameter url: `URL` being downloaded.
/// - Returns: Publisher for the URL with final destination of the downloaded asset. Returns `nil` if request failed.

func downloadPublisher(for url: URL) -> AnyPublisher<URL?, Never> {
    URLSession.shared.downloadTaskPublisher(for: url)
        .tryCompactMap {
            let destination = try FileManager.default
                .url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
                .appendingPathComponent(url.lastPathComponent)
            try FileManager.default.moveItem(at: $0.location, to: destination)
            return destination
        }
        .replaceError(with: nil)
        .receive(on: RunLoop.main)
        .eraseToAnyPublisher()
}

/// Publisher for a series of downloads
///
/// This downloads not more than `maxConcurrent` assets at a given time.
///
/// - Parameters:
///   - urls: Array of `URL`s of assets to be downloaded.
///   - maxConcurrent: The maximum number of downloads to run at any given time (default 4).
/// - Returns: Publisher for the URLs with final destination of the downloaded assets.

func downloadsPublisher(for urls: [URL], maxConcurrent: Int = 4) -> AnyPublisher<URL?, Never> {
    Publishers.Sequence(sequence: urls.map { downloadPublisher(for: $0) })
        .flatMap(maxPublishers: .max(maxConcurrent)) { $0 }
        .eraseToAnyPublisher()
}

這是相當簡約和純粹的方法。 沒有 NSOperationQueue(),只是 didSet-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()
        }
    }

在視圖控制器中:

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://blog.medium.com/39-reasons-we-wont-soon-forget-2016-154ac95683af#.cmb37i58b",
                       "https://backchannel.com/in-2017-your-coworkers-will-live-everywhere-ae14979b5255#.wmi6hxk9p"]
    }



}

后台情況不止一個代碼。 我可以通過使用的全局變量和 NSTimer 來學習。 你也可以試試。

定義“indexDownloaded”全局變量。

import UIKit
import Foundation

private let _sharedUpdateStatus = UpdateStatus()
class UpdateStatus : NSObject  {

// MARK: - SHARED INSTANCE
class var shared : UpdateStatus {
    return _sharedUpdateStatus
}
  var indexDownloaded = 0
}

此代碼添加到 DownloadOperation 類中。

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

這個函數在你的 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)
    }

這樣,通過更新進度視圖,每次計時器運行時都會查看下載的數量。

Rob 的回答顯示了正確的方法來做到這一點。 我實現了基於委托的方式來跟蹤下載進度視圖。

你可以在這里查看源代碼。 帶進度條的多次下載(Github)

Objective-C 版本是:

[operation2 addDependency:operation1]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM