mankee mankee - 3 months ago 14
iOS Question

Downloading Data From URL Swift Asynchronously

I have an array of 5 links which contain either

jpg
or
mov
files. I want to download them into my iOS Swift app. I use the following code.

var index = 0
while (index < myLinks.count) {
if (myLinks[index].resource_type == "image") {
let stringURL = "http://www.blablabla" + ".jpg"
let url = NSURL(string : stringURL)
getDataFromUrl(url!) { (data, response, error) in
guard let data = data where error == nil else { return }
print(response?.suggestedFilename ?? url!.lastPathComponent ?? "")
print("Download Finished")
myLinks[index].image = UIImage(data: data)
}
}
else if (myLinks[index].resource_type == "video") {
let stringURL = "http://www.blablabla" + ".mov"
let url = NSURL(string : stringURL)
getDataFromUrl(url!) { (data, response, error) in
guard let data = data where error == nil else { return }
print(response?.suggestedFilename ?? url!.lastPathComponent ?? "")
print("Download Finished")
dispatch_async(dispatch_get_main_queue(), {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsDirectory : NSString = paths[0]
let appFile = documentsDirectory.stringByAppendingPathComponent(myLinks[index].id! + ".m4v")
do {
try data.writeToFile(appFile, options: .AtomicWrite)
myLinks[index].videoURL = NSURL(fileURLWithPath: appFile)
}
catch _ {
print("error saving video")
}
})
}
}

index = index + 1
}


func getDataFromUrl(url: NSURL, completion: ((data: NSData?, response: NSURLResponse?, error: NSError? ) -> Void)) {
NSURLSession.sharedSession().dataTaskWithURL(url) {
(data, response, error) in
completion(data: data, response: response, error: error)
}.resume()
}


Now the only problem is that, after downloading these resources, I want to display them to the user in a form of slideshow, hence I can only proceed knowing that all 5 downloads have been completed. The problem with the code above is that since the
getDataFromUrl
function is executed asynchronously, this while loop will finish executing before all of the content has actually been downloaded.

Answer

Using the built in abilities of NSURLSession and its underlying NSOperationQueue you can do this easily enough.

import UIKit

class ViewController: UIViewController {

    let MyDownloadsCompleteNotification:String = "MyDownloadsCompleteNotification"

    var myLinks:[NSURL]?
    var downloadQueue:[NSURL]?
    let session = NSURLSession.sharedSession()
    let lockQueue = dispatch_queue_create("com.dsdevelop.lockQueue", nil)

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        myLinks = [ NSURL(string: "https://pbs.twimg.com/profile_images/447374371917922304/P4BzupWu.jpeg")!,
                    NSURL(string: "http://www.telegraph.co.uk/content/dam/pets/2016/03/18/bunny-large_trans++qVzuuqpFlyLIwiB6NTmJwfSVWeZ_vEN7c6bHu2jJnT8.jpg")!,
                    NSURL(string: "http://4.bp.blogspot.com/-HTvSYzA-pO4/UgQb4Zh_u0I/AAAAAAAAEuI/XwhtogT_1tA/s1600/3+cute2.jpg")!,
                    NSURL(string: "http://cdn.shopify.com/s/files/1/0224/1915/files/bunny.jpg?22110")!,
                    NSURL(string: "http://vignette1.wikia.nocookie.net/hare/images/1/1f/Bunnies-bunny-rabbits-16437969-1280-800.jpg/revision/latest?cb=20130831183111")!
        ]


        // Register to know when all downloads are done
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(allDownloadsComplete), name: MyDownloadsCompleteNotification, object: nil)

        downloadAllLinks(myLinks!)
    }

    func allDownloadsComplete(n:NSNotification)
    {
        print("Awesome all downloads are done!")
    }

    func getRemainingActiveDownloads() -> Int
    {
        return (self.downloadQueue != nil) ? self.downloadQueue!.count : 0
    }

    func removeUrlFromDownloadQueue(url:NSURL)
    {
        if self.downloadQueue != nil
        {
            dispatch_sync(lockQueue) {
                self.downloadQueue = self.downloadQueue!.filter({$0.absoluteString == url.absoluteString})
            }
        }
    }

    func downloadAllLinks(links:[NSURL])
    {
        // suspending queue so they don't all finish before we can show it
        session.delegateQueue.suspended = true
        session.delegateQueue.maxConcurrentOperationCount = 1

        downloadQueue = links

        // create tasks
        for link in links
        {
            let dltask = session.downloadTaskWithURL(link, completionHandler: { (url, response, error) in
                if let urlString = response?.URL?.absoluteString
                {
                    self.downloadQueue = self.downloadQueue!.filter({$0.absoluteString != urlString})
                    let remaining = self.getRemainingActiveDownloads()
                    print("Downloaded.  Remaining: \(remaining)")

                    if (remaining == 0)
                    {
                        NSNotificationCenter.defaultCenter().postNotificationName(self.MyDownloadsCompleteNotification, object: nil)
                    }

                }
            })
            print("Queuing task \(dltask)")
            dltask.resume()
        }

        // resuming queue so all tasks run
        session.delegateQueue.suspended = false
    }
}
Comments