Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Whole Session download progress Alamofire

Alamofire is being used to download multiple larger files at one request. I am able to make progress be seen for each file separately using

Alamofire.request(.GET, imageURL).progress

But I would like to track the progress of all open sessions at once and do not know how to do that. (lets say I have 15 files downloading simultaneously) I read a lot of tutorials that address single file progress but for whole session none.

Is it possible to track progress that way with Alamofire and if it is, how?

like image 514
Jernej Mihalic Avatar asked Dec 14 '15 09:12

Jernej Mihalic


1 Answers

In iOS9, you can create your own NSProgress object, and observe, for example, fractionCompleted. Then you can addChild:

private var observerContext = 0

class ViewController: UIViewController {

    private var progress: NSProgress!

    override func viewDidLoad() {
        super.viewDidLoad()

        progress = NSProgress()
        progress.addObserver(self, forKeyPath: "fractionCompleted", options: .New, context: &observerContext)

        downloadFiles()
    }

    deinit {
        progress?.removeObserver(self, forKeyPath: "fractionCompleted")
    }

    private func downloadFiles() {
        let filenames = ["as17-134-20380.jpg", "as17-140-21497.jpg", "as17-148-22727.jpg"]

        let baseURL = NSURL(string: "http://example.com/path")!

        progress.totalUnitCount = Int64(filenames.count)
        progress.completedUnitCount = 0

        for filename in filenames {
            let url = baseURL.URLByAppendingPathComponent(filename)
            let childProgress = Alamofire.request(.GET, url.absoluteString)
                .response() { request, response, data, error in
                    // process response
                }
                .progress
            progress.addChild(childProgress, withPendingUnitCount: 1)
        }
    }

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if context == &observerContext {
            if keyPath == "fractionCompleted" {
                let percent = change![NSKeyValueChangeNewKey] as! Double
                print("\(percent)")
            }
        } else {
            super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
        }
    }

}

If you need support for iOS 7/8, too, you can call becomeCurrentWithPendingUnitCount and resignCurrent:

for filename in filenames {
    let url = baseURL.URLByAppendingPathComponent(filename)
    progress.becomeCurrentWithPendingUnitCount(1)
    Alamofire.request(.GET, url.absoluteString)
        .response() { request, response, data, error in
            // process response
    }
    progress.resignCurrent()
}

If you are using AFNetworking, it's the same process (i.e. the same viewDidLoad, observeValueForKeyPath, and deinit methods as above), but rather than retrieving the Alamofire progress property, you instead use the AFHTTPSessionManager method downloadProgressForTask to get the NSProgress associated with the NSURLSessionTask. For example:

private func getFiles() {
    let filenames = ["as17-134-20380.jpg", "as17-140-21497.jpg", "as17-148-22727.jpg"]

    let manager = AFHTTPSessionManager()
    manager.responseSerializer = AFHTTPResponseSerializer()

    let baseURL = NSURL(string: "http://example.com/path")!

    progress.totalUnitCount = Int64(filenames.count)
    progress.completedUnitCount = 0

    for filename in filenames {
        let url = baseURL.URLByAppendingPathComponent(filename)
        let task = manager.GET(url.absoluteString, parameters: nil, progress: nil, success: { task, responseObject in
            // do something with responseObject
            print(url.lastPathComponent! + " succeeded")
        }, failure: { task, error in
            // do something with error
            print(error)
        })

        if let downloadTask = task, let childProgress = manager.downloadProgressForTask(downloadTask) {
            progress.addChild(childProgress, withPendingUnitCount: 1)
        }
    }
}

Or, if using download tasks:

private func downloadFiles() {
    let filenames = ["as17-134-20380.jpg", "as17-140-21497.jpg", "as17-148-22727.jpg"]

    let manager = AFHTTPSessionManager()

    let baseURL = NSURL(string: "http://example.com/path")!

    progress.totalUnitCount = Int64(filenames.count)
    progress.completedUnitCount = 0

    let documents = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)

    for filename in filenames {
        let url = baseURL.URLByAppendingPathComponent(filename)
        let task = manager.downloadTaskWithRequest(NSURLRequest(URL: url), progress: nil, destination: { (temporaryURL, response) -> NSURL in
            return documents.URLByAppendingPathComponent(url.lastPathComponent!)
        }, completionHandler: { response, url, error in
            guard error == nil else {
                print(error)
                return
            }

            if let name = url?.lastPathComponent {
                print("\(name) succeeded")
            }
        })

        if let childProgress = manager.downloadProgressForTask(task) {
            progress.addChild(childProgress, withPendingUnitCount: 1)
        }

        task.resume()
    }
}
like image 160
Rob Avatar answered Nov 15 '22 01:11

Rob