1

Our iPhone app code currently uses NSURLConnection sendSynchronousRequest and that works fine except we need more visibility into the connection progress and caching so we're moving to an async NSURLConnection.

What's the simplest way to wait for the async code to complete? Wrap it in a NSOperation/NSOperationQueue, performSelector..., or what?

Thanks.

denton
  • 553
  • 1
  • 6
  • 8

4 Answers4

4

I'm answering this in case anyone else bumps into the issue in the future. Apple's URLCache sample code is a fine example of how this is done. You can find it at:

As John points out in the comment above - don't block/wait - notify.

Thomas C. G. de Vilhena
  • 13,819
  • 3
  • 50
  • 44
denton
  • 553
  • 1
  • 6
  • 8
2

I ran into this because our app used NSURLConnection sendSynchronousRequest in quite a few places where it made sense, like having some processing occurring on a background thread occasionally needing extra data to complete the processing. Something like this:

// do some processing
NSData * data = someCachedData;
if (data = nil) {
    data = [NSURLConnection sendSynchronousRequest....]
    someCachedData = data;
}
// Use data for further processing

If you have something like 3 different places in the same flow that do that, breaking it up into separate functions might not be desirable(or simply not doable if you have a large enough code base).

At some point, we needed to have a delegate for our connections(to do SSL certificate pinning) and I went trolling the internet for solutions and everything was of the form: "just use async and don't fight the framework!". Well, sendSynchronousRequest exists for a reason, this is how to reproduce it with an underlying async connection:

 + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse *__autoreleasing *)response error:(NSError *__autoreleasing *)error
 {
     static NSOperationQueue * requestsQueue;
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
         requestsQueue = [[NSOperationQueue alloc] init];
         requestsQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
     });

     NSCondition * waitLock = [NSCondition new];
     [waitLock lock];

     __block NSError * returnedError;
     __block NSURLResponse * returnedResponse;
     __block NSData * returnedData;
     __block BOOL done = NO;
     [NSURLConnection sendAsynchronousRequest:request
                                        queue:requestsQueue
                            completionHandler:^(NSURLResponse * response, NSData * data, NSError * connectionError){

                     returnedError = connectionError;
                     returnedResponse = response;
                     returnedData = data;
                     [waitLock lock];
                     done = YES;
                     [waitLock signal];
                     [waitLock unlock];
                 }];
     if (!done) {
         [waitLock wait];
     }
     [waitLock unlock];
     *response = returnedResponse;
     *error = returnedError;
     return returnedData;
 }

Posted here in case anyone comes looking as I did.

Note that NSURLConnection sendAsynchrounousRequest can be replaced by whatever way you use to send an async request, like creating an NSURLConnection object with a delegate or something.

entropy
  • 3,134
  • 20
  • 20
2

To use NSURLConnection asynchronously you supply a delegate when you init it in initWithRequest:delegate:. The delegate should implement the NSURLConnection delegate methods. NSURLConnection processing takes place on another thread but the delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object.

m4rkk
  • 511
  • 2
  • 10
  • Hi, Right, got that. But, the bit I'm wrestling with is how to wait/block in the other thread until the request is complete. Thanks. – denton May 05 '09 at 15:03
  • 3
    @denton you don't want to block. When the request completes you notify your main (or other) thread. NSNotificationCenter is one way. – John Fricker May 05 '09 at 16:00
2

Apart from notifications mentioned prior, a common approach is to have the class that needs to know about the URL load finishing set itself as a delegate of the class that's handling the URL callbacks. Then when the URL load is finished the delegate is called and told the load has completed.

Indeed, if you blocked the thread the connection would never go anywhere since it works on the same thread (yes, even if you are using the asynch methods).

Kendall Helmstetter Gelner
  • 74,769
  • 26
  • 128
  • 150