Search code examples
iospostasynchronousswiftnsurlconnection

NSURLConnection sendAsynchronousRequest can't get variable out of closure


I'm trying to get a simple text response from a PHP page using POST. I have the following code:

func post(url: String, info: String) -> String {
    var URL: NSURL = NSURL(string: url)!
    var request:NSMutableURLRequest = NSMutableURLRequest(URL:URL)
    var output = "Nothing Returned";
    request.HTTPMethod = "POST";
    var bodyData = info;
    request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);

    NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){

        response, data, error in

        output = (NSString(data: data, encoding: NSUTF8StringEncoding))!


    }

    return output
}

While this code does not throw any errors, when I make a call to it like this:

println(post(url, info: data))

It only prints: "Nothing Returned" even though if I were to change the line:

output = (NSString(data: data, encoding: NSUTF8StringEncoding))!

to this:

println((NSString(data: data, encoding: NSUTF8StringEncoding)))

it does print out the proper response. Am I doing something wrong with my variables here?


Solution

  • This is calling asynchronous function that is using a completion handler block/closure. So, you need to employ the completion handler pattern in your own code. This consists of changing the method return type to Void and adding a new completionHandler closure that will be called when the asynchronous call is done:

    func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) {
        let URL = NSURL(string: url)!
        let request = NSMutableURLRequest(URL:URL)
        request.HTTPMethod = "POST"
        let bodyData = info
        request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
    
        NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { response, data, error in
            guard data != nil else {
                completionHandler(nil, error)
                return
            }
    
            completionHandler(NSString(data: data!, encoding: NSUTF8StringEncoding), nil)
        }
    }
    

    Or, since NSURLConnection is now formally deprecated, it might be better to use NSURLSession:

    func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) -> NSURLSessionTask {
        let URL = NSURL(string: url)!
        let request = NSMutableURLRequest(URL:URL)
        request.HTTPMethod = "POST"
        let bodyData = info
        request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
    
        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
            dispatch_async(dispatch_get_main_queue()) {
                guard data != nil else {
                    completionHandler(nil, error)
                    return
                }
    
                completionHandler(NSString(data: data!, encoding: NSUTF8StringEncoding), nil)
            }
        }
        task.resume()
    
        return task
    }
    

    And you call it like so:

    post(url, info: info) { responseString, error in
        guard responseString != nil else {
            print(error)
            return
        }
    
        // use responseString here
    }
    
    // but don't try to use response string here ... the above closure will be called
    // asynchronously (i.e. later)
    

    Note, to keep this simple, I've employed the trailing closure syntax (see Trailing Closure section of The Swift Programming Language: Closures), but hopefully it illustrates the idea: You cannot immediately return the result of an asynchronous method, so provide a completion handler closure that will be called when the asynchronous method is done.