1

I have an iPad application where I am making asynchronous calls using NSURLConnection. In some cases I receive all the response data in connection:didReceiveData:, but connectionDidFinishLoading is never called. There is no error. This is somewhat random, because the same responses do complete at other times.

The way that my class works is that around 20 requests are sent in a row using:

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];

And then I just wait for them to come back. Is this a valid way to create multiple requests?

Here is a sample header of a response that did not complete. It is indistinguishable from the header of a response that did complete.

>Keep-Alive: timeout=5, max=100
>Transfer-Encoding: Identity
>Server: Apache/2.2.9 (Unix) mod_ssl/2.2.9 OpenSSL/0.9.8b mod_jk/1.2.26
>Content-Type: application/json;charset=UTF-8
>Connection: Keep-Alive
>Date: Wed, 03 Apr 2013 05:25:32 GMT
>Cache-Control: private, no-transform, max-age=600

One weird symptom of the problem is that I started checking the expected content length using:

long long download_size =[response expectedContentLength];

The Content-Length is never set in the http header. When a request fails the download_size is -1 (expected), when the same request does not fail the download_size is set to some number. However, there are many cases where the download_size is not set and the response does not fail.

minus
  • 320
  • 1
  • 5
  • 14

2 Answers2

1

I do hope you are use separate objects here. You are saying

NSURLConnection *connection = 
  [[NSURLConnection alloc] initWithRequest:request 
     delegate:self startImmediately:YES];

Well, I sure hope those are 20 different self objects. You must not expect this to work with a single object acting as the delegate for all 20 requests simultaneously!

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I thought the responses would queue. I am changing my implementation to test this. – minus Apr 03 '13 at 16:59
  • I have a class that can help you: see the MyDownload class here http://www.apeth.com/iOSBook/ch37.html#_http_requests – matt Apr 03 '13 at 17:13
  • However, I'm also going to suggest that you not clog up the thread / networking system by trying to do 20 downloads at once! – matt Apr 03 '13 at 17:14
1

This is not a great way to initiate 20 requests because:

  1. The requests operate concurrently, and thus if you don't have some class that encapsulates all of the response data and the like from the individual requests, your app can be confusing the response data from various requests;

  2. Because they happen concurrently and because iOS only allows five concurrent requests to a given server, it will delay (and possibly timeout) the others.

You have a bunch of different approaches, but you probably:

  • You might want to be doing this network stuff on a background queue;

  • If you want concurrent operations (and there's an observable performance benefit from doing so), you could use NSOperationQueue to have concurrent operations, but limit how many concurrent operations are ongoing (to 4 or 5) with the use of maxConcurrentOperationCount. This process is trivial if you're using synchronous network operations in these background operations, but surprisingly complicated if you use asynchronous network operations with your own delegate methods in the background queue, though.

  • If you really need (a) to use the asynchronous network calls using your own delegate methods (e.g. updating progress views, streaming protocols, etc.); and (b) you want to enjoy concurrent operations, it will be considerably easier to use AFNetworking than it will be to write your own. I went through this exercise of writing my own, but having done that exercise once, I now more fully appreciate what AFNetworking brings to the table.

Sure, you could get around all of this by managing your own array of pending network requests, initiate the first one, and have the connectionDidFinishLoading and/or didFailWithError kick off the next one in your queue, but you lose the performance gain of concurrency. But it is one simple work-around.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • I have changed my app to create a new class for each connection. The connection delegate class lives long enough to pass success or fail to my view controller. Thanks for the advice - I am wondering where you got the info that iOS only allows five concurrent requests. – minus Apr 03 '13 at 22:04
  • 1
    @CarolineM In [MVCNetworking](https://developer.apple.com/library/ios/#samplecode/MVCNetworking/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010443), Apple uses four. When I did benchmarking (to see how much performance improved by increasing max concurrent), I found overall perf improved as max concurrent increased, less dramatically as max concurrent approached 5, but with more than that, if it took more than 1 minute to start they'd fail). I read somewhere on S.O. that 5 was hard, single-host iOS limit (which I haven't seen in Apple docs), but it's consistent with what I've experienced. – Rob Apr 03 '13 at 22:42
  • You generally only experience this if downloading big files or over slow connection (use network conditioner on simulator or turn off wifi); i.e. something that takes more than one minute to complete the first connections before the latter operations have a chance to start. – Rob Apr 03 '13 at 22:45
  • Yes, I have been looking around and I haven't been able to see anything about it in the docs, but it seems to be what people are saying. I see - my requests are very small, which is why I am not noticing it. – minus Apr 03 '13 at 22:52
  • @CarolineM I know that servers can be configured individually, so it might also depend upon your server's settings. I also searched online and I've seen figures ranging from 4 to 6 bandied about. The performance gain seems to be diminished beyond 4 concurrent, so there may be little benefit in going beyond that regardless. You should test your app with Edge network. Or download the "Hardware IO Tools for Xcode" (you can find this on the "Xcode" menu - "Open Developer Tool" - "More Developer Tools") and install the "Network Link Conditioner" and you can simulate slow network on the simulator. – Rob Apr 03 '13 at 23:03