How to do synchronous network calls if NSURLConnection.sendSynchronousRequest is depricated

Greetings,

Apple: Thanks for all the hard work you have been doing to make iOS easier for developers. A lot of great things are happening. Keep up the good work!


I noticed that in iOS 9 (Xcode 7.0 beta 7A121l), Apple is depreciating NSURLConnection.sendSynchronousRequest in both Swift, and Objective-C. Here is the warning I am seeing:


Swift warning:

'sendSynchronousRequest:_:returningResponse:)' was deprecated in iOS 9.0 - Use [NSURLSession dataTaskWithRequest:completionHandler:] (see NSURLSession.h


Objective-C warning:

'sendSynchronousRequest:returningResponse:error:' is deprecated: first deprecated in iOS 9.0 - Use [NSURLSession dataTaskWithRequest:completionHandler:] (see NSURLSession.h


Please let me know if I am missing something, but I think it is a huge mistake for Apple to depricate the NSURLConnection.sendSynchronousRequest method.


Your reasons for depricating may not handle all the "real world" use cases. I am imagining that your justification for depricating NSURLConnection.sendSynchronousRequest is that you want us to use NSURLSession because it is asynchronous, and you want to discourage synchronous network requests. I am very familiar with NSURLSession, and have used it a lot to do asynchronous network requests. However, there is a definite, "real world" use case it does not handle. Here is some pseudo code that describes the use case where we need NSURLConnection.sendSynchronousRequest:


dispatch async or whatever way we want to get off the main ui thread {

Make network calls synchronously to fetch multiple accounts (outer loop) {

Make network calls synchronously to fetch products in each account (inner loop) {

}

}

reload UI on main UI thread

}


In the above example, I need to make multiple synchronous calls, serially. I can do that off the main UI thread. However, they must be done synchronously because the network calls in the inner loop rely on the output from the first call in the outer loop.

For the above mentioned used case, here are my available UNACCEPTABLE solutions (that I know of).


1) Use a dispatch_semaphore with NSURLSession. I think this is a very bad option. In my opinion this is unacceptable because it's going to create a lot of extra code on my end (the customer) and be difficult to maintain, and more prone to bugs. I think that the dispatch_semaphore with NSURLSession is very clunky and unnecessary since we already have NSURLConnection.sendSynchronousRequest: which works great, is very little code, and very easy for me (the customer) to maintain, and use. This is a very bad for me, possible solution, that's going to be extremely difficult to implement and maintain.


2) NSData(contentsOfURL. This is not sophisticated enough because it does not allow me to build up a NSMutableURLRequest. This does not work.


3) Use NSURLSession. This won't work because it is asynchronous. I need to fetch the accounts first, then iterate through each account to fetch products, and it needs to be done synchronously. The wrapper around the whole thing can be done off the main UI thread, but what is inside needs to be done synchronously. This does not work.


Therefore, I would strongly urge that Apple not depricate NSURLConnection.sendSynchronousRequest because it breaks code that already works, and does not handle the "real world" use case I mentioned above, which would run off of the main UI thread.


Thanks in advance for looking at this.

Accepted Reply

So as a work around to get it to collect all my data (without being rate limited) I throttle down the rate of the requests that will rate limit me with a 2 second sleep for each of those calls, and run all my requests synchronously, in serial.

Wow, so you're deliberately trying to make your networking slow (-: As you might imagine, that puts you somewhat outside of the target market for mainstream networking APIs.

If you just want a drop-in replacement for

+sendSynchronousRequest:***
, that's pretty easy to implement within NSURLSession. We don't provide it as an API because we think it's a really bad idea—for all the reasons we've been discussing in this thread—but given your rather unique constraints it probably make sense.

Earlier you wrote:

Use a dispatch_semaphore with NSURLSession. I think this is a very bad option. In my opinion this is unacceptable because it's going to create a lot of extra code on my end (the customer) and be difficult to maintain, and more prone to bugs.

I think you're radically overestimating how much code this will be. Here's a drop-in replacement in less than a page. Critically, this requires only a trivial change at the call site (replacing

NSURLConnection
with
QNSURLConnection
).
@implementation QNSURLConnection

+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request
    returningResponse:(__autoreleasing NSURLResponse **)responsePtr
    error:(__autoreleasing NSError **)errorPtr {
    dispatch_semaphore_t    sem;
    __block NSData *        result;

    result = nil;

    sem = dispatch_semaphore_create(0);

    [[[NSURLSession sharedSession] dataTaskWithRequest:request
        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (errorPtr != NULL) {
            *errorPtr = error;
        }
        if (responsePtr != NULL) {
            *responsePtr = response;
        }
        if (error == nil) {
            result = data;
        }
        dispatch_semaphore_signal(sem);
    }] resume];

    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

   return result;
}


@end

Now, I'm not recommending you go down this path. The correct solution, IMO, is to fix the server and, from there, rewrite your client to use it efficiently. However, I agree that, in the absence of a server-side fix, it's pretty pointless rewriting all your client code because it's not actually going to yield significant benefits.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Here is some pseudo code that describes the use case where we need NSURLConnection.sendSynchronousRequest:

I think you need to work on your example here. The problem is that you're adding serialisation here where you don't need it, and that undermines your argument because you're saying that "synchronous requests are cool because they let me do things really inefficiently" )-:

Consider the two account case:

  1. fetch account A

  2. fetch products A1 through An

  3. fetch account B

  4. fetch products B1 through Bm

Why does step 3 have to wait for step 2 to finish? By itself that's not a huge problem, but the fact that it puts all of step 4, which presumably represents a lot of work, behind step 2 is likely to be a problem.

The issue here is latency. By taking a synchronous approach you're introducing unnecessary serialisation which means that the network latency is unnecessarily delaying the overall task.

A better approach to this would be:

  1. send request for account A

  2. when you get account A, send request for product A1 and account B

  3. when you get account B, send request for product B1

  4. as product Xy completes, send a request for product X(y+1)

This assumes that you can't get account B without first getting account A, and that you can't get product X(y+1) without first getting product Xy. If you have any control over your on-the-wire protocol, you could radically reduce the time taken for this task by removing a bunch of requests:

  1. send request for account list

  2. when you have the account list, send a request for each account's product list

Changes like this can radically speed up your network operations, especially over high latency networks like WWAN. We're not talking small numbers here. The latency for a WWAN can easily hit 400 ms, so 100 serialised requests will take a minimum of 40 seconds whereas 100 parallel requests can be done in under a second.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Mr. Quinn (The Eskimo!),


Thanks for your rapid response! I truly appreciated it. However, your solution still does not help me.


For my use case, I don't care whatsoever about speed. Speed is completely irrelevant in this case. What I need is a syncronous solution because I have network calls that depend on the results of previous network calls. This can go 3 layers deep in my case.


Knowing that, do you have any other ideas?

For my use case, I don't care whatsoever about speed. Speed is completely irrelevant in this case.

Interesting. How many requests are you making in total? Do you have some inside knowledge that these requests are going to be done on a low-latency network?

Having spent much of my career working at the wrong end of a high-latency network, I'm very aware of how bad latency issues can be. For example, MPW's source code control system, Projector, was built on top of a network file system, and thus behaved abysmally over a high-latency link. You could be waiting until the end of time to check out a project. Things were much better when we switched to CVS.

Knowing that, do you have any other ideas?

I'm happy to come back to this later but I'd really like to nail down the performance issue first.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

"For my use case, I don't care whatsoever about speed. Speed is completely irrelevant in this case. What I need is a syncronous solution because I have network calls that depend on the results of previous network calls. This can go 3 layers deep in my case."


Reading through this thread, it sounds like you've got a misconception of how asynchronous code works.


Your original psuedo code:

dispatch async or whatever way we want to get off the main ui thread {

Make network calls synchronously to fetch multiple accounts (outer loop) {

Make network calls synchronously to fetch products in each account (inner loop) {

}

}

reload UI on main UI thread

}


Proper asynchronous code:

Make asynchronous call to fetch multiple accounts with completion handler {

Make asynchronous calls to fetch products for each account with completion handler {

create list of products, and dispatch asynch addition of (account, product list) info onto main thread when done

}

}


Three layers deep is nothing, you could go arbitrarily deeply nested. And if you wanted to limit your code to requesting one account at a time, you still don't need a complicated semaphore system to do it. You just have to follow the Cocoa design motto "Unsynchronized is fine if no one else touches the code."

So

  1. Put your list of requests to make on a custom object AccountRequester.
  2. Call a method on that object to initiate the first request, and have the handler method on AccountRequester.
  3. The handler on AccountRequester generates the asynchronous request for the next account when it receives the data for the previous request.


Even if you've got this massively nested loop of synchronous requests where the inner most request needs to access variables five or six layers out, you can rewrite that into a series of completion handlers that pass the necessary parameters between each other.

Mr. Quinn, and Mr. NotMyName,


Thank you very much for looking at this.


To answer some of your questions, I am making around 850 network calls with my internal/Enterprise type app. My situation is this. If I go async, and attempt to hit the 3rd party back end Restful Web Service Server (which I don't control) with a lot of rapid requests, their server will rate limit me and kill ALL of my requests. So as a work around to get it to collect all my data (without being rate limited) I throttle down the rate of the requests that will rate limit me with a 2 second sleep for each of those calls, and run all my requests synchronously, in serial. It takes about 10 minutes to get data back on all the 850 web service calls, then I cache it on the device. So even though it is slow the first time, everything is super fast after that, because it is stored on the device.


Even though I am very experienced in doing async calls with NSURLSession, I found that using NSURLConnection.sendSynchronousRequest worked perfectly as a simple solution for my app. I believe NSURLConnection.sendSynchronousRequest has been around in iOS for over 6 years. I am sure it is out there in a lot of existing code. I feel NSURLConnection.sendSynchronousRequest is like an old trusted friend that did what I needed perfectly. Now I am being told that NSURLConnection.sendSynchronousRequest is being depricated and I must have another shiny new friend forced on me named: NSURLSession. Even though my shiny new friend NSURLSession can do many wonderful things, he doesn't do the one thing my old friend could do (lock so I can do a sendSynchronousRequest). This may be convenient for Apple, but it is terrible for me as a iOS developer.


This is causing me to rapidly go through the 5 stages of the grieving process: denial, anger, bargaining, depression, acceptance.


With 1 line of code: NSURLConnection.sendSynchronousRequest, I can do exactly what I need to do, and it's simple. If I can't convince Apple to leave NSURLConnection.sendSynchronousRequest in iOS, I have to invent my own async queueing system to chain my layers of network calls that rely on other network calls, and keep tabs on when everything finishes. I know this will be a more complicated rewrite of already complicated code, and challenging to debug. From my perspective, it feels very harsh that Apple is depricating a critical networking function that has been a cornerstone of iOS for 6+ years. It feels like I can't build because everything is changing out from under me. Are you sure you are taking into consideration the use case that some network calls will be slow and rate limited (not high performers)? Some situations may need a simple synchronous solution. Also just because someone is doing multiple, chained, synchronous calls, it doesn't necessarily mean they are locking they main UI thread. There could be other things going on to get it off the main UI thread.


Do you agree that in general, callbacks that call callbacks, that call callbacks, lead to all types of trouble?


Anyway, why do you need to depricate NSURLConnection.sendSynchronousRequest since it already works, is simple, has a locking mechanism, is powerful, and had been around for 6+ years? Why not just leave it in iOS permanently and continue your NSURLSession improvements?


Thanks.

So as a work around to get it to collect all my data (without being rate limited) I throttle down the rate of the requests that will rate limit me with a 2 second sleep for each of those calls, and run all my requests synchronously, in serial.

Wow, so you're deliberately trying to make your networking slow (-: As you might imagine, that puts you somewhat outside of the target market for mainstream networking APIs.

If you just want a drop-in replacement for

+sendSynchronousRequest:***
, that's pretty easy to implement within NSURLSession. We don't provide it as an API because we think it's a really bad idea—for all the reasons we've been discussing in this thread—but given your rather unique constraints it probably make sense.

Earlier you wrote:

Use a dispatch_semaphore with NSURLSession. I think this is a very bad option. In my opinion this is unacceptable because it's going to create a lot of extra code on my end (the customer) and be difficult to maintain, and more prone to bugs.

I think you're radically overestimating how much code this will be. Here's a drop-in replacement in less than a page. Critically, this requires only a trivial change at the call site (replacing

NSURLConnection
with
QNSURLConnection
).
@implementation QNSURLConnection

+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request
    returningResponse:(__autoreleasing NSURLResponse **)responsePtr
    error:(__autoreleasing NSError **)errorPtr {
    dispatch_semaphore_t    sem;
    __block NSData *        result;

    result = nil;

    sem = dispatch_semaphore_create(0);

    [[[NSURLSession sharedSession] dataTaskWithRequest:request
        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (errorPtr != NULL) {
            *errorPtr = error;
        }
        if (responsePtr != NULL) {
            *responsePtr = response;
        }
        if (error == nil) {
            result = data;
        }
        dispatch_semaphore_signal(sem);
    }] resume];

    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

   return result;
}


@end

Now, I'm not recommending you go down this path. The correct solution, IMO, is to fix the server and, from there, rewrite your client to use it efficiently. However, I agree that, in the absence of a server-side fix, it's pretty pointless rewriting all your client code because it's not actually going to yield significant benefits.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Mr. Quinn (The Eskimo!),


I want to try your idea, it looks very promising. Thank you for taking the time, I really appreciate it, and your help!


As far as my situation, I think that there may be other similar rate limited, or synchronous situations out there with other developers that are just like mine. Where a simple synchronous solution is needed. I think a wrapper (to get it off the main UI thread) around serial synchronous requests is a viable use case people are going to still need. You may not know the entire universe of use cases. In that case, it would be better to leave useful things that work, and let the SDK developers decide if they need it or not.


To me it still makes no sense from my perspective as a SDK developer, why you would depricate something that is 1 line, works, has been tested, and has been around for 6+ years for something that is 33 lines, and doesn't have 6 years of testing behind it. Not every situation is going to be an async high speed network server. As you can see in my situation (which by the way is tightly associated with Apple), sometimes you have to throttle down network request. From my perspective as a SDK developer, I would prefer 1 line of code officially supported by Apple, over 33 lines supported by me (since I might mess it up). I still don't think you should depricate NSURLConnection.sendSynchronousRequest because you don't know how many people out there still need it, and because there are so many other things about iOS and Mac OSX that are unstable and buggy right now. It's too much. If you must, then put something in NSURLSession for syncronous calls.


Thanks.

something that is 1 line, works, has been tested, and has been around for 6+ years for something that is 33 lines

Your line count comparison is not valid because you're comparing the call site in one case with the implementation in the other. Presumably you have more call sites than implementations (-:

To make a valid comparison you'd have to know how many lines make up

+sendSynchronousRequest:***
. That's kinda hard to work out due to layering issues but I got to 274 lines before I gave up.

Alas, this isn't open source so I can't show you my workings.

I should also point out that we didn't just deprecate

+sendSynchronousRequest:***
, we deprecated all of NSURLConnection. There are very good reasons to do this, regardless of the state of our
+sendSynchronousRequest:***
. The real question is, when we introduced NSURLSession, why didn't we introduce a synchronous convenience method. And that's the subject of the rest of this post.

Notably, even within the NSURLConnection world,

+sendSynchronousRequest:***
was effectively replaced by
+sendAsynchronousRequest:***
back in iOS 5.

There's a bunch of issues with running the network synchronously. These include:

main thread problems — Folks doing synchronous networking on the main thread is a significant cause of crashes (on iOS) and SPODs (on OS X). It's particularly insidious because a lot of developers who are just starting out write code this way, test it only in their office (where everything works fine), and then don't see problems until they deploy their app (where it's hardest to debug).

We seriously considered modifying

+sendSynchronousRequest:***
to simply fail if you called it on the main thread, but that discussion was obviated by its deprecation.

on-the-wire parallelism — On-the-wire parallelism is critical for network performance, and it's hard to get using a synchronous API.

Note that, with the advent of HTTP/2, on-the-wire parallelism is becoming more relevant and more effective.

local resource usage — Folks often try to get around the on-the-wire parallelism issue by using multiple threads, each of which runs one synchronous request. This is a really bad idea because threads are expensive, so having a bunch of threads blocked waiting for the network is a significant waste of resources.

Worse yet, if you're using GCD it's possible to deadlock your app this way because there are limits to how many worker threads that GCD will spin up on your behalf.

This problem occurs enough that it has a fancy name, "thread explosion". WWDC 2015 Session 718 Building Responsive and Efficient Apps with GCD talks about this.

  • lack of cancellation — There's just no way to cancel a request that's stuck in

    +sendSynchronousRequest:***
    . In theory we could create a new API within the NSURLSession space but that seems like heading in the wrong direction.
  • timeouts -- Somewhat related to the previous point, synchronous networking relies on good timeout support (because you can't simply cancel the request at a time of your choosing). It's hard to build a timeout system that meets everyone's needs.

[There are other issues here but they're not directly relevant to the high-level HTTP APIs that we're discussing].

Now, in your particular situation you are specifically trying to avoid on-the-wire parallelism, so two of these points are irrelevant to you. However, I don't think your app represents a typical case. The vast number of developers I talk to are interested in making their networking run as fast as possible.

Moreover, artificially serialising and delaying network requests is not a massive challenge in any of the three common async networking paradigms:

  • In GCD code, you can use a serial queue for serialisation and

    dispatch_after
    for delays.
  • In main thread networking, serialisation is automatic and you have NSTimer (or

    -performSelector:afterDelay:***
    ) for delays.
  • In NSOperation, you can create a serial queue for your serialisation and a simple run loop operation for delays.

You can also use NSURLSession's

HTTPMaximumConnectionsPerHost
feature to serialise requests.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Mr. Eskimo,


I can confirm that your Jul 23, 2015 1:37 AM solution works!


I really appreciate your help.


Thanks.