10

I have the following function which downloads an image from server;

func getImageFromServerById(imageId: String) -> UIImage? {
    let url:String = "https://dummyUrl.com/\(imageId).jpg"
    var resultInNSDataformat: NSData!

    let task = NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!) {(data, response, error) in
        if (error == nil){
            resultInNSDataformat = data
        }
    }
    task.resume()
    return UIImage(data: resultInNSDataformat)
}

The function does not wait for the download task to be completed before returning the image. Therefore my app always crashes. Any ideas for how to wait for the download?

Berkan Ercan
  • 1,207
  • 3
  • 14
  • 31

1 Answers1

20

The other answer is not a good replacement for the code you already had. A better way would be to continue using NSURLSession's data tasks to keep the download operation asynchronous and adding your own callback block to the method. You need to understand that the contents of the download task's block are not executed before you return from your method. Just look at where the call to resume() is for further evidence.

Instead, I recommend something like this:

func getImageFromServerById(imageId: String, completion: ((image: UIImage?) -> Void)) {
    let url:String = "https://dummyUrl.com/\(imageId).jpg"

    let task = NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!) {(data, response, error) in
        completion(image: UIImage(data: data))
    }

    task.resume()
}

Which can be called like this

getImageFromServerById("some string") { image in
    dispatch_async(dispatch_get_main_queue()) {
        // go to something on the main thread with the image like setting to UIImageView
    }
}
Mick MacCallum
  • 129,200
  • 40
  • 280
  • 281
  • 1
    @LeonardoSavioDabus I'm not sure what you mean. This code executes a completion block after the task has completed and as long as code handling the image is moved to the completion block, it will work as expected. – Mick MacCallum Nov 30 '14 at 00:29
  • He wants the task to complete before something else happens. Does that make sense? I'm searching for an answer as well. – Mihado Dec 09 '15 at 21:48
  • @Mihado Both of these answers facilitate that, just in different ways. My answer keeps the networking operations executing asynchronously off of the main thread and provides a callback to let you know when the download is complete. Leo's answer downloads the image synchronously on which ever thread the method was called on, meaning that code below the method call won't be executed until after the download is complete. If you call this method from the main thread, it will also cause the UI to lock up until the download finishes, which will have consequences. – Mick MacCallum Dec 09 '15 at 23:08
  • @Mihado I go into a lot more detail about this [here](http://stackoverflow.com/a/13100042/716216). – Mick MacCallum Dec 09 '15 at 23:09
  • I will examine your link. With my issue I have a login page and I don't want the login page (the view controller) to dismiss until the user has been authenticated. It would seem very weird if it dismissed and showed the initial view controller before you were authenticated. I hope that makes sense. – Mihado Dec 09 '15 at 23:11
  • Okay, I read through the entirety of your answer on the link provided. It's extremely informative but doesn't answer my question. On my login view controller I'm looking to wait till a response comes back from nsurlsession before I dismiss the view controller and show the initial view controller (the view that the user sees if they're logged in). I know nsurlsession executes on a background thread and doesn't block the UI, but in a sense, I want it to block the UI. – Mihado Dec 09 '15 at 23:50