54

I have been using NSURLConnection's sendAsynchronousRequest:queue:completionHandler: method which is great. But, I now need to make multiple requests in a row.

How can I do this while still using this great asychronous method?

Nic Hubbard
  • 41,587
  • 63
  • 251
  • 412

1 Answers1

109

There's lots of ways you can do this depending on the behavior you want.

You can send a bunch of asynchronous requests at once, track the number of requests that have been completed, and do something once they're all done:

NSInteger outstandingRequests = [requestsArray count];
for (NSURLRequest *request in requestsArray) {
    [NSURLConnection sendAsynchronousRequest:request 
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        [self doSomethingWithData:data];
        outstandingRequests--;
        if (outstandingRequests == 0) {
            [self doSomethingElse];
        }
    }];
}

You could chain the blocks together:

NSMutableArray *dataArray = [NSMutableArray array];    
__block (^handler)(NSURLResponse *response, NSData *data, NSError *error);

NSInteger currentRequestIndex = 0;
handler = ^{
    [dataArray addObject:data];
    currentRequestIndex++;
    if (currentRequestIndex < [requestsArray count]) {
        [NSURLConnection sendAsynchronousRequest:[requestsArray objectAtIndex:currentRequestIndex] 
                                   queue:[NSOperationQueue mainQueue]
                       completionHandler:handler];
    } else {
        [self doSomethingElse];
    }
};
[NSURLConnection sendAsynchronousRequest:[requestsArray objectAtIndex:0] 
                                   queue:[NSOperationQueue mainQueue]
                       completionHandler:handler];

Or you could do all the requests synchronously in an ansynchronous block:

dispatch_queue_t callerQueue = dispatch_get_current_queue();
dispatch_queue_t downloadQueue = dispatch_queue_create("Lots of requests", NULL);
    dispatch_async(downloadQueue, ^{
        for (NSRURLRequest *request in requestsArray) {
            [dataArray addObject:[NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]];
        }
        dispatch_async(callerQueue, ^{
            [self doSomethingWithDataArray:dataArray];
        });
    });
});

P.S. If you use any of these you should add some error checking.

yuji
  • 16,695
  • 4
  • 63
  • 64
  • Doesn't dispatching a synchronous request in a dispatch_asycn cause memory leaks? – Nic Hubbard Feb 23 '12 at 09:06
  • I guess I was under the impression that synchronous request in a background thread was bad. But using dispatch_async isn't really a background thread? – Nic Hubbard Feb 23 '12 at 09:15
  • 4
    `dispatch_async` here **is using** a background thread. But there's nothing wrong with calling synchronous requests from a background request. In fact, the [documentation](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html) explicitly discourages you from making synchronous requests in the main thread of a GUI app since it'll block the UI. – yuji Feb 23 '12 at 09:17
  • 2
    @yuji,after reading the sendAsynchronousRequest documentation, and looking at your first option above, I am still unclear on the queue definition in that method. Is this the queue that will be used for the download and as such should not be the main queue such as you have it above or will the async download happen by definition on some other queue other then the main queue and the queue in the method should be the main queue to do post download tasks. Thanks KMB – Khaled Barazi Sep 12 '12 at 21:40
  • 3
    `sendAsynchronousRequest:queue:completionHandler:` sends a request asynchronously (i.e., in the background, not blocking the main thread). When the response comes back, the handler block is dispatched to the queue specified in the queue specified. – yuji Sep 16 '12 at 18:46
  • I am using [NSURLConnection sendAsynchronousRequest:pRequest queue:bpUrlRequestQueue completionHandler:^(NSURLResponse *pResponse, NSData *pData, NSError *pError) { // do something with the data, all commented out } ); But my memory keep increasing and eventually crash after memory warning... How do I release the NSData *pData? I thought it was autoreleased, but the memory increase so fast and so much I can't think of other leak.. – Zennichimaro Jan 21 '13 at 15:02
  • Out of interest, in the first code example, how is one sure that access to `outstandingRequests` is thread-safe? What's to stop both completionHandlers trying to alter it at the same time? – Carlos P Aug 20 '13 at 11:27
  • 2
    @CarlosP That's a non-issue because the `completionHandler`s in this example are not being executed in parallel threads. They get added to the `mainQueue` and executed one at a time. There's presumably some kind of multithreading going on behind the scenes in Cocoa's internals to handle putting the blocks onto the `mainQueue` as the HTTP responses arrive, but it's abstracted away and you don't need to worry about it. If you've worked with AJAX in JavaScript before, this behaviour with sequential execution of completion handlers will be familiar to you. – Mark Amery Aug 27 '13 at 14:27
  • NO. You could create a separate background queue if you need to have 2 completion blocks executed sequentially. Even if you will call `dispatch_async` inside these blocks with main queue param then they will still be executed one at a time. If you need them to be executed with a specified order then you need something like reactive cocoa or promiseKit – Vyachaslav Gerchicov Dec 21 '16 at 15:08