Getting data out of completionHandler in Swift in NSURLConnection
Asked Answered
C

1

13

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!

Courtroom answered 17/7, 2014 at 0:9 Comment(4)
How about var dataOutput: NSData!?Littlest
when I use var dataOutput: NSData!, I get the following error: fatal error: unexpectedly found nil while unwrapping an Optional value. I feel like this is probably because it is an asynchronous request, and does not immediately have a value to return. this leads me to believe that my second edit is probably the better way to goCourtroom
but good to know! does simply implicitly unwrapping a variable allow it to be accessed within nested functions? i facepalmed when i saw your response :DCourtroom
When doing things async, you can't return synchronously. So you should do like your second edit. Using optional lets you skip initializing because compiler will silently init with nil. Instead of using optional you can init your variable, but in your case initializing using empty data is wasteful.Littlest
C
6

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.

Consult answered 17/7, 2014 at 2:34 Comment(4)
Thank you Undo, that was very helpful. I have added more code to the main body above that gives a bigger picture of the problem.Courtroom
Thanks! I'm a little busy at the moment, I'll try to get back to this in a day or two. In the meantime, I think you need to look into the concept of delegates and protocols. That should give you a good start.Consult
Hey Undo, Did you ever get a chance to take a look at this again?Courtroom
@Courtroom can you give an update on how you finally solved the problem?Delfinadelfine

© 2022 - 2024 — McMap. All rights reserved.