Chris
Chris

Reputation: 7310

Making threaded NSURLConnections

I have a lot of connections going when my app starts, so I wanna put them on background threads so I can make new connections other than the starting connections before they all complete.

Below, threadedRequest: is a method that's starting a NSURLConnection, but when I call performSelectorInBackground:withObject: in the if clause, the connection starts, but never finishes. The else clause works fine and returns data from the connection

if (background)
{
    [self performSelectorInBackground: @selector(threadedRequest:) withObject: args];
}
else
{
    [self performSelector: @selector(threadedRequest:) onThread: [NSThread mainThread] withObject: args waitUntilDone: NO];
}

Upvotes: 1

Views: 226

Answers (1)

Rob
Rob

Reputation: 438212

If you want to perform NSURLConnection in the background, you must be sensitive to special conditions that apply to NSURLConnectionDataDelegate methods in background threads. You have several options:

  1. Use one of the non-delegate alternatives. Given that you're performing this in the background, you could use sendSynchronousRequest, or if just requesting data from a simple URL, you could use NSData class method dataWithURL.

  2. If you really need the delegate version (because you need progress updates via didReceiveData or because you need one of the NSURLConnectDelegate methods for authentication or the like, you have a couple of basic options:

    • You can create a NSOperationQueue and set the delegate queue for the connection. For example, rather than:

      - (void)startConnection:(NSURL *)url
      {
          NSURLRequest *request = [NSURLRequest requestWithURL:url];
          [NSURLConnection connectionWithRequest:request delegate:self];
      }
      

      You could do:

      - (void)startConnection:(NSURL *)url
      {
          NSURLRequest *request = [NSURLRequest requestWithURL:url];
          NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
          [connection setDelegateQueue:self.connectionDelegateQueue];
          [connection start];
      }
      
    • Alternatively, you could do what AFNetworking does (see AFURLConnectionOperation.m, for sample implementation):

      • Create a dedicated NSThread for the NSURLConnectionDataDelegate calls;

      • Start a NSRunLoop on that thread;

      • Use the same startImmediately:NO rendition of the NSURLConnection init method as above; and

      • Use the scheduleInRunLoop option for the NSURLConnection before you start it.

    • Or, easiest, you could just use AFNetworking

Two observations:

  1. I might be inclined to use operation or dispatch queues instead of performSelectorInBackground. See Concurrency Programming Guide.

  2. By the way, you should be aware that there is a limit as to how many concurrent NSURLConnection requests iOS can perform simultaneously (it's 5 or 6, I believe). Thus, if you initiate a dozen background requests and then initiate a "foreground" request, the "foreground" request may not start immediately. You may want to limit how many background requests that can operate concurrently if you want to avoid this potential problem.

    Personally, when I want to constrain the number of network requests, I add my background requests to a NSOperationQueue and set the number of maximum concurrent operations (e.g. I generally use 4), enjoying the benefits of concurrent operations, while not trying to perform more simultaneous connections than iOS will permit. By the way, when availing yourself of operation queue's maxConcurrentOperations feature, you have to make sure that the requests are synchronous (or wrap the asynchronous connection in a custom NSOperation that won't terminate until the connection is done or fails).

Upvotes: 1

Related Questions