perhapsmaybeharry
perhapsmaybeharry

Reputation: 894

What is the way to get received data out of URLSession?

Recently, I attempted to write my own Telegram Bot API. However, the project has seem to have hit a brick wall with URLSession (formerly NSURLSession) issues.

The call structure is as follows:

getMe() -> getData() -> NSURLSession

Ideally, I would like to have the data returned from NSURLSession passed back to getMe() for the application to process. However, this has not proven possible with the methods I have tried.

Below is the code I have been using. synthesiseURL() generates the URL that the app should open the session to in order to perform the action on the Telegram Bot API. A template of the URL generated by synthesiseURL() is https://api.telegram.org/bot\(token)/\(tgMethod).

// NSURLSession getData: gets data from Telegram Bot API
func getData(tgMethod: String, arguments: [String] = [String](), caller: String = #function) {
    let url = synthesiseURL(tgMethod: "getMe"), request = NSMutableURLRequest(url: url)

    var receivedData = String()

    let session = URLSession.shared.dataTask(with: request as URLRequest) { data, response, err in

        if err != nil {print(err!.localizedDescription); return}

        DispatchQueue.main.async {
            receivedData = String(data: data!, encoding: String.Encoding.nonLossyASCII)!
            print(receivedData)
        }
    }

    session.resume()
}

I have been trying to get getData to pass receivedData, which contains the Bot API's response, back to the function getMe.

func getMe() -> String {
    HTTPInterface(botToken: token).get(tgMethod: "getMe")
    return [???] // here's where the data from getData() should come
}

I have tried completion handlers, callbacks, asynchronous calls to the main thread etc, but none seem to be working as expected (getMe() returns an empty string).

Why is this so, and can it be fixed?

Upvotes: 1

Views: 453

Answers (1)

Daniel Hall
Daniel Hall

Reputation: 13679

The fundamental issue is that your getMe() function is declared as having an immediate String return type, but it depends on a delayed / asynchronous call to get that string. The timeline looks something like this:

  1. getMe() is called by some client code
  2. getMe() kicks of the method that launches a URLSession to get the data
  3. getMe() moves to the next line of execution and returns a string (still empty at this point). The getMe() function has now returned and the client code execution has continued forward with the empty String result
  4. The URLSession completes with data, but execution has already moved on so the data doesn't get used anywhere

The easiest fix is to make your getMe function not have a return type, but to also call back to a closure parameter when the URLSession data comes back, something like:

func getMe(callback:String->()) {
     //getData and pass a closure that executes the callback closure with the String data that comes back
}

The less easy fix is to use a technique like dispatch semaphores to prevent getMe() from returning a result until the URLSession data comes back. But this sort of approach is likely to stall your main thread and is unlikely to be the right choice.

Upvotes: 1

Related Questions