Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting data out of completionHandler in Swift in NSURLConnection

I am trying to write a function that will execute an asynchronous GET request, and return the response (as any data type, but here it is as NSData).

This question is based on: How to use NSURLConnection completionHandler with swift

func getAsynchData() -> NSData {
    var dataOutput : NSData
    let url:NSURL = NSURL(string:"some url")
    let request:NSURLRequest = NSURLRequest(URL:url)
    let queue:NSOperationQueue = NSOperationQueue()

    NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
            /* this next line gives the below error */
            dataOutput = data
    })
    return dataOutput
}

but I get an error:

error: variable 'dataOutput' captured by a closure before being initialized

I have tried returning the value from the completionHandler, but it requires a void return, which eerily reminds me of my hope to solve this problem without help... :D

I have looked at: How to use completionHandler Closure with return in Swift? but this does not really answer my question. My objective here is to get the data from my asynchronous request out of the block, for use elsewhere in my code. Am I supposed to do all of the work with this request in this block and not get the data out?

Thank you!

EDIT

ok so I have an option which I think might work, but it doesn't seem right to me. Can someone tell me if this is the best way of accomplishing my goal?

func doThingsWithData( data: NSData ) -> String {
    /* code to extract string from NSData */
    return somestring
}
func getAsynchData() {
    let url:NSURL = NSURL(string:"some url")
    let request:NSURLRequest = NSURLRequest(URL:url)
    let queue:NSOperationQueue = NSOperationQueue()

    NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
            /* this next line gives the below error */
            doThingsWithData(data)
    })
}

EDIT 2 -> responding to undo

Thanks, Undo. Your answer makes sense to me. Here is more of the puzzle. I have a class that is my API handler. I want to be able to instantiate that class, and call a function on it to get data from the API. I would rather get all the data with one api call, rather than making separate calls each time for each value I need to get out, as a single API call contains all the data I need, but that might be a whole other answer. Here is the code:

class GetInfoFromAPI {

    func getSpecificValue(index : String) -> String {
        /* I assume I need to send the values from this function, yea? but how do I get them here? */
    }

    func doThingsWithData( data: NSData ) -> String {
        /* code to extract string from NSData */
        var error: NSError?
        let jsonDict = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as NSDictionary

        specificValue1 : String = jsonDict.valueForKey("value1") as String
        specificValue2 : String = jsonDict.valueForKey("value2") as String
        specificValue3 : String = jsonDict.valueForKey("value3") as String

        /* I want to get these ^^^ values into the ViewController below */
    }

    func getAsynchData() {
        let url:NSURL = NSURL(string:"some url")
        let request:NSURLRequest = NSURLRequest(URL:url)
        let queue:NSOperationQueue = NSOperationQueue()

        NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
            /* this next line gives the below error */
            doThingsWithData(data)
        })
    }
}


class ViewController: UIViewController {

    @IBOutlet var labelVariable1: UILabel
    @IBOutlet var labelVariable2: UILabel
    @IBOutlet var labelVariable3: UILabel

    let apiInstance = GetInfoFromAPI()

    @IBAction func buttonTapped(sender : AnyObject) {
        labelVariable1 = apiInstance.getSpecificValue(1)
        labelVariable2 = apiInstance.getSpecificValue(2)
        labelVariable3 = apiInstance.getSpecificValue(3)
    }

}

Thank you for taking time to answer my questions. I am new to swift, and stack overflow is immensely helpful!

like image 811
cosmikwolf Avatar asked Jul 17 '14 00:07

cosmikwolf


1 Answers

Let me try to explain this - it's a misunderstanding of threading, one I myself struggled with at first. I'm going to try to simplify things a little bit. When you run this code:

NSLog("Log 1")

NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
        NSLog("Log in Completion Block")
})

NSLog("Log after request")

You're going to get output that looks like this:

Log 1
Log after request
Log in completion block

Let me make a chart, kind of a timeline:

                    "Log 1" (before request is sent)
                                       |
                                       |
                           Request sent over Internet
                                       |
                                     /   \  
                   "Log after request"   |
               This is logged *before*   | 
           the other one, because this   |
          one doesn't have to wait for   |
               the network to respond.   |
                                         |
The method finishes and returns a value. |
------------------------------------------------------------------------------
                                         | The network finally responds,
                                         | and the completion block is run.
                                         |

                                         "Log in completion block"

The vertical line is where the method finishes and returns. In all cases, your method will have already returned a value to its caller before your completion block is run. You can't think about this linearly.

Am I supposed to do all of the work with this request in this block and not get the data out?

Yes, essentially. I can help more if you show me the code that calls getAsynchData() in the first place.

like image 187
Undo Avatar answered Oct 05 '22 07:10

Undo