Search code examples
iphoneswiftfor-loopnsurlsessionnsdocumentdirectory

NSURLSession in a loop


I was hoping to run this code to fetch multiple images from URLs but although the loop runs through every object in my array, the only object that is saved is the final one. Is there a better way to do this?

EDIT: It seems that all the images saved from the different URLs, but they all saved with the same filename, the final filename in the loop. This would seem to be caused by the delay that by the time NSURLSession and writing the image to file happens, the filename has already been set to the final one of the loop.

for object in objectArray {

     url = NSURL(string:object.urlToLoad)
     imageFile = paths.stringByAppendingPathComponent("\(object.filename).png")

     let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
          data.writeToFile(imageFile, atomically: true)
          return
     }

     task.resume()
}

Thanks for any help!


Solution

  • The problem is that they are all capturing/sharing the same imageFile variable, as it is declared outside the scope of the for. So first you go over the loop n times, overwriting the imageFile variable with the next filename as you go and firing off an asynchronous download. Then, later, by the time each of the n closures actually execute on completion of their download, they are all referencing the same filename, the one matching the last value in the array.

    Try sticking a let in front of it, thus declaring a fresh local variable with every iteration, that each closure in turn will capture. You should do the same with the url as well, even though it happens not to be a problem.

    Generally, this is another good example of how you should use let rather than var at every opportunity, and to avoid the practice of re-using variables in outer scopes except when you specifically want to communicate data to the outer scope.

    Here's some standalone code that demonstrates this without the URL-downloading aspect, just 5 sleep-and-prints running in parallel:

    import Dispatch
    let q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
    
    var shared: String = "shared=x"
    for x: UInt32 in 1...5 {
    
        shared = "shared=\(x)"
        let unique = "unique=\(x)"
        dispatch_async(q) {
            sleep(x)
            println("\(shared), \(unique)")
        }
    
    }
    
    println("for loop completed, \(shared)")
    dispatch_main()