2

I'm trying to use Coinbase's API to get information about my online bitcoin wallet, and I'm trying to use Swift's NSURLSession object to do so. Perhaps I'm missing something obvious in the Apple docs, but after reading through the information about NSURLSession and NSURLSessionTask I still do not understand how to make an HTTP request and then return the body of the response so that the body can persist throughout the life of my app. As of now I only see the ability to use completion blocks which return void, or delegates which either return void themselves or use completion blocks which also return void. I want to use the data I get from the response later in the app, but because I'm using completion blocks I must handle the response data immediately after the response arrives.

To make it clear, I want to do something along the lines of the pseudocode function below:

func makeHTTPCall(urlString : String) -> String? {
    create NSURLSession object
    create request with proper headers and using the passed-in urlString
    use the session object to send out the request 
    get the response object, extract the response body as a string, and return it
}

Then later, I could call something like this:

let myObject : MyObject = MyObject()
let respData : String = myObject.makeHTTPCall("https://coinbase.com/api/v1/account/balance")

This data is returning a JSON Object string, which is the String I want to persist beyond the life of the response and its completion block. How can I do this in either Swift or Objective C, since I'll be able to use either in Xcode 6?

EDIT: Two answers have been posted, but they miss the fundamental point of this question. I need to RETURN the data which I receive from the response. Both answers (and all other answers I've seen on SO) simply print the data received. I would like code that doesn't use a void-returning completion handler, but instead returns the data so that it can be used later in the lifecycle of the app. If there is anything unclear about my question, please tell me, though I don't see how this can be made clearer.

5
  • stackoverflow.com/questions/24055850/afnetworking-and-swift Commented Jul 16, 2014 at 15:34
  • So, the question is really "how to persist data"? Commented Jul 16, 2014 at 15:36
  • Thank you! This is the first sane explanation of why the completion handlers exist. This is the problem though; how can you make completion handlers modular, and adhere to DRY? My app will make many different API calls, and deal with the responses from those calls in different ways. If I'm using completion handlers, I'll have to write identical code for setting the headers, creating the NSURLSession and NSURLDataTask objects, and actually dealing with the response in the handler. I'll end up writing much of the same code, and this feels wrong. Commented Jul 17, 2014 at 2:11
  • 1
    I'd suggest you consider moving that "identical code" into a method/function, so you're not writing that code multiple times. E.g. have a method that initiates NSURLSessionTask, does some common shared stuff in the completion handler, but then passes the request-specific stuff in another completion block variable (or closure) that you'll invoke inside the completionHandler of the dataTaskWithRequest. Effectively, implement your own method with completion handler so you can have method abstract away the common stuff. Commented Jul 17, 2014 at 2:55
  • 1
    " I would like code that doesn't use a void-returning completion handler, but instead returns the data so that it can be used later in the lifecycle of the app" Meaning, you want a synchronous method, which means you will block while the network request is in progress. That is frowned upon. Commented Jul 17, 2014 at 3:12

4 Answers 4

4

In the edit to your question, you say:

I need to RETURN the data which I receive from the response. Both answers (and all other answers I've seen on SO) simply print the data received. I would like code that doesn't use a void-returning completion handler, but instead returns the data so that it can be used later in the lifecycle of the app. If there is anything unclear about my question, please tell me, though I don't see how this can be made clearer.

I understand the appeal of this strategy, because it feels so intuitively logical. The problem is that your networking requests should always run asynchronously (e.g. use that completion handler pattern to which you allude).

While there are techniques making a function "wait" for the asynchronous request to complete (i.e. to make the asynchronous NSURLSession method behave synchronously or use one of the old synchronous network request methods), this is a really bad idea for a number of reasons:

  • If you do this from the main thread, it results in a horrible user experience (the app will be unresponsive while the request is in progress and the user won't know if the app is busy doing something or whether it's frozen for some unknown reason).

  • Again, if you do this from the main thread, you also risk having the iOS "watch dog" process kill your app (because if you block the main queue for more than a few seconds at the wrong time, particularly as the app comes to foreground, the OS will unceremoniously terminate your app). See Technical Q&A #1693 for a discussion on the problems of doing synchronous network requests.

  • We generally prefer the asynchronous network techniques because they offer more features unavailable with synchronous techniques (e.g. making requests cancelable, offer progress updates when using delegate-based network requests, etc.).

You really should use the completion handler pattern that those other questions suggest, and manage the changing state of the app in those handlers. In those situations where you absolutely cannot let the user proceed until some network request is done (e.g. you can't let the user buy something until you confirm their bitcoin balance, and you can't do that until they log in), then change the UI to indicate that such a request is in progress. For example, dim the UI, disable the controls, pop up an activity indicator view (a.k.a., a "spinner"), etc. Only then would you initiate the request. And upon completion of the request, you would restore the UI. I know it seems like a lot, but it's the right way to do it when the user absolutely cannot proceed until the prior request is done.

I'd also think long and hard as to whether it's truly the case that you absolutely have to force the user to wait for the prior network request to complete. Sometimes there are situations where you can let the user do/review something else while the network request is in progress. Yes, sometimes that isn't possible, but if you can find those sorts of opportunities in your app, you'll end up with a more elegant UX.

Sign up to request clarification or add additional context in comments.

3 Comments

Uh, the examples you cite are specifically about synchronous networking on the main thread. Synchronous networking on another thread will not block the user experience. It's still not a good practice, but will not get your app watchdog'd or have the user waiting.
@quellish Agreed. (FYI, that is explicitly contemplated in Q&A 1693 in which they advise either do it asynchronously from the main thread or synchronously from background thread.) But I really don't suspect that the OP is saying "I'm doing request in a background thread and want to do it synchronously in that thread". If he is, we can help him do that (but even that is a less-than-ideal solution, but for other reasons). This feels like a far more fundamental question about why people use completion blocks with these asynchronous methods.
Thank you Rob and quellish, I've finally got it through my thick skull why Apple was making it so hard for me to return data about the response. I chose Rob's answer because it supplied the link to the watchdog information which really made me understand the asynchronous request design. Thank you both.
2

I know that problem and use this code for synchronous requests:

func synchronousRequest() -> NSDictionary {

        //creating the request
        let url: NSURL! = NSURL(string: "exampledomain/...")
        var request = NSMutableURLRequest(URL: url)
        request.HTTPMethod = "GET"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")


        var error: NSError?

        var response: NSURLResponse?

        let urlData = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)

        error = nil
        let resultDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(urlData!, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary

        return resultDictionary
    }

Comments

0

What you are asking for is a synchronous network request. There are many ways to do this, such as... NSData's init(contentsOfURL aURL: NSURL!) NSURLConnection's synchronous request method ...etc.

These methods will block the current thread until they complete - which can be a potentially long time. Network requests can have very high timeouts, it may be several minutes before the device gives up. NSData's init with contents of URL will return NSData, not void, and does not execute asynchronously. It will block until it is complete, which is why it's recommended to not do these types of requests from the main thread. The UI will be frozen until it completes.

In general the use of synchronous networking methods is discouraged. Asynchronous network requests are greatly preferred for a number of reasons. Using an asynchronous method that takes a completion block as a parameter will not prevent you from using the returned data elsewhere in your application. The block is executed when the network request has finished (wether it succeeds or fails) and it is passed the data, response metadata, and error. You are free to do what you want with that data - nothing prevents you from persisting it, passing it off to another object, etc. Based on your comments it sounds like you want to take the data that was the result of the network request and set it as the value of a property somewhere - that is entirely doable using an asynchronous method that uses a block as a completion handler.

Comments

-1

In objective-C you can use __block and get the data when the operation finishes:

__block NSData *myData;
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:urlString]
      completionHandler:^(NSData *data,
                          NSURLResponse *response,
                          NSError *error) {
        myData = data;

}] resume];

1 Comment

No, there really is little benefit to using __block in this scenario. The __block qualifier is really only useful when doing synchronous requests, but the network request runs asynchronously, meaning that the myData local variable will be falling out of any useful scope well before the completion handler is called. Generally, you'd use your own completion handler pattern (have the completionHandler of dataTaskWithURL call your own block/closure), not use a __block variable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.