NSURLConnection sendAsynchronousRequest can't get variable out of closure
Asked Answered
A

1

4

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?

Ardis answered 26/10, 2014 at 4:11 Comment(1)
sendAsynchronousRequest is an asynchronous request so you wont get the value of output right after the block. You need to implement a callback mechanism in order to handle it.Shinberg
E
8

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.

Eerie answered 26/10, 2014 at 4:53 Comment(3)
Thanks Rob. I believe you've answered my question, however do you think I should use sendAsynchronousRequest? I'm using this for a login script, so maybe sendSynchronousRequest is better?Ardis
You almost never want to use sendSynchronousRequest (and the only place you would use it is from background thread, where it's effectively asynchronously with respect to the main thread, anyway). Use sendAsynchronousRequest, like above, and put any code contingent upon the successful login inside the completion handler of the post call. This is the heart of all network programming: do it asynchronously and put dependent code in the completion handlers.Eerie
Or, as pointed out in the revision to this answer, you should not use NSURLConnection at all, anymore, because it's now deprecated. You really should be using NSURLSession, which doesn't even offer a synchronous rendition (which is a good design decision, IMHO).Eerie

© 2022 - 2024 — McMap. All rights reserved.