Wait for download task to finish in NSURLSession

ph1psG picture ph1psG · Oct 5, 2015 · Viewed 8k times · Source

My scenario looks like this: I have a class which has a function in it and this function makes a POST request to a server with NSURLSession. In another function which is not in that class I call that function with the task in it, which runs asynchronously I think. I'm encountering following problem: When I download the JSON response from the server and write it to a variable and return it, the variable I've written to is still nil since it didn't wait for the thread/task to finish downloading and returns the initial value which is nil.

So my question is how can I wait for that task to finish and then continue the whole program.

This is my class with the request function:

class DownloadTask {
var json:NSDictionary!
var cookie:String!
var responseString : NSString = ""

func forData(completion: (NSString) -> ()) {

    var theJSONData:NSData!
    do {
        theJSONData = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions())
    } catch _ {
        print("error")
    }

    let request = NSMutableURLRequest(URL: url)
    request.HTTPMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    if cookie != nil {
        request.setValue(cookie, forHTTPHeaderField: "Cookie")
    }
    request.HTTPBody = theJSONData

    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
        data, response, error in

        if error != nil {
            print("error")
            return
        } else {
            completion(NSString(data: data!, encoding: NSUTF8StringEncoding)!)
        }
    }
    task.resume()
}

}

And this is my calling code:

let task = DownloadTask()
task.forData { jsonString in
    do {
        if let json: NSDictionary = try NSJSONSerialization.JSONObjectWithData(jsonString.dataUsingEncoding(NSUTF8StringEncoding)!, options:  NSJSONReadingOptions.MutableContainers) as? NSDictionary {
            if json["error"] != nil {
                authenticated = false
            } else {
                let result = json["result"] as! NSDictionary
                user.personId = result["personId"] as! NSNumber
                user.sessionId = result["sessionId"] as! NSString
                print("test0")
                authenticated = true
            }
        }
    }
    catch _ {
        print("error")
    }
}
print("test1")

The output is:

test1
test0

And I can't acess the variables user.sessionId after that task.

Answer

ph1psG picture ph1psG · Jan 4, 2016

As I said before in the comments of the main post the download NSURLSession.sharedSession().dataTaskWithRequest(request) run synchronously to the main thread which means it downloads the data synchronously to the displaying. So in short the print("test0") will be executed when the dispatched thread is finished (aka downloaded the data). To fix this I had to use semaphores (I also combined the first code and the second code from the original question):

private func downloadData(completion: (String) -> ()) {
   let semaphore = dispatch_semaphore_create(0);
   let request = NSMutableURLRequest(URL: url)

   var theJSONData = NSData()
   do {
       theJSONData = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions())
   } catch {
       completion(String())
   }

   request.HTTPMethod = "POST"
   request.setValue("application/json", forHTTPHeaderField: "Content-Type")
   request.HTTPBody = theJSONData

   let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
       data, response, error in
       data == nil ? completion(String()) : completion(String(data: data!, encoding: NSUTF8StringEncoding)!)
       dispatch_semaphore_signal(semaphore);
   }
   task.resume()
   dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}

This may not be the most efficient nor the most elegant solution but in my case I really had to wait for the downloading to be finished else it shouldn't do anything.