iosswiftuser-interfacedispatch-asyncbackground-thread

Updating UI Dispatch_Async background download Swift


Im working on a folders/files application where users are able to download files to local disk. Whenever a user is downloading a file, I want to show a download bar that displays progress.

to do so, I've created a protocol that allows my download class and my view controller to communicate:

protocol:

protocol DownloadResponder : class {
    func downloadFinished()
    func downloadProgress(current:Int64, total:Int64)
}

download class:

class fileDownloader: NSObject, NSURLSessionDelegate, NSURLSessionDownloadDelegate {

    //responder
    var responder : MyAwesomeDownloadResponder?

    init(responder : MyAwesomeDownloadResponder) {
        self.responder = responder
    }

    ...

    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {

        println("downloaded \(100*totalBytesWritten/totalBytesExpectedToWrite)")
        responder?.downloadProgress(totalBytesWritten, total: totalBytesExpectedToWrite)

    }

...

}

and then in my view controller I have my download button which trigger the downloadProgress function:

func downloadProgress(current:Int64, total:Int64) {
        let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
        dispatch_async(dispatch_get_global_queue(priority, 0)) {
            // do some task
            var currentProgress = 100 * current / total
            dispatch_async(dispatch_get_main_queue()) {
                // update some UI
                self.downloadLbl.text = "Downloaded \(currentProgress)%"
                //set progress bar
                self.progressBar.setProgress(Float(currentProgress), animated: true)
            }
        }

    }

While printing information in the console works all the time, updating the UI was not really stable. To fix this I used the dispatch_async method that push the UI change on the main thread. However, while it always work on the first time, poping back to the previous view controller and coming back again, executing the download once more does not trigger the UI updates. The progress bar progressBar.setProgress does nothing and my label downloadLbl.text does not update itself.

Does anyone have an idea about the way to solve this? If my question lacks information, please let me know and I'll try to add up to the existing information. Thanks!


Solution

  • As I didn't receive / find any solution to my problem I went back to an higher level and changed the way to communicate between my classes to handle ui changes based on background download thread progression.

    Instead of using protocols, I went for Notifications and it solved my problem.

    Inside the download class:

    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
    
            println("downloaded \(100*totalBytesWritten/totalBytesExpectedToWrite)")
    
            //NOTIFICATION
            // notify download progress!
            var fileInfo = [NSObject:AnyObject]()
            fileInfo["fileId"] = fileDownloader.storageInfo[downloadTask.taskIdentifier]!["id"] as! Int!
            fileInfo["fileCurrent"] = Float(totalBytesWritten)
            fileInfo["fileTotal"] = Float(totalBytesExpectedToWrite)
    
            let defaultCenter = NSNotificationCenter.defaultCenter()
            defaultCenter.postNotificationName("DownloadProgressNotification",
                object: nil,
                userInfo: fileInfo)
    
        }
    

    inside the view controller:

    override func viewDidLoad() {
            super.viewDidLoad()
    
            // ready for receiving notification
            let defaultCenter = NSNotificationCenter.defaultCenter()
            defaultCenter.addObserver(self,
                selector: "handleCompleteDownload:",
                name: "DownloadProgressNotification",
                object: nil)
        }
    
    func handleCompleteDownload(notification: NSNotification) {
            let tmp : [NSObject : AnyObject] = notification.userInfo!
    
            // if notification received, change label value
            var id = tmp["fileId"] as! Int!
            var current = tmp["fileCurrent"] as! Float!
            var total = tmp["fileTotal"] as! Float!
            var floatCounter = 100 * current / total
            var progressCounter = String(format: "%.f", floatCounter)
    
            if(id == self.fileId){
                let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
                        dispatch_async(dispatch_get_global_queue(priority, 0)) {
                            // do some task
                            dispatch_async(dispatch_get_main_queue()) {
                                // update some UI
                                self.downloadLbl.text = "Downloaded \(progressCounter)%"
                                self.progressBar.setProgress((progressCounter as NSString).floatValue, animated: true)
                            }
                        }
            }
        }
    

    hope that will help!