NSURLConnection NSURLRequest proxy for asynchronous web service calls

tbehunin picture tbehunin · Dec 24, 2009 · Viewed 31.1k times · Source

I have multiple views which make the same NSURLRequest/NSURLConnection request. Ideally, in order to get some code reuse, I'd like to have some sort of a "proxy" which does all the underlying work of creating/executing the (asynchronous) request/connection, setting up all the delegate methods, etc., so I don't have to copy all those NSURLConnection delegate method handlers in each view. First of all, is this design approach reasonable? Second, how would I go about doing something like that?

For a little background info, I attempted this and got it to "work", however, it doesn't appear to be executing asynchronously. I created a Proxy.h/m file which has instance methods for the different web service calls (and also contains the NSURLConnection delegate methods):

@interface Proxy : NSObject {

    NSMutableData *responseData;
    id<WSResponseProtocol> delegate;
}

- (void)searchForSomethingAsync:(NSString *)searchString delegate:(id<WSResponseProtocol>)delegateObj;

@property (nonatomic, retain) NSMutableData *responseData;
@property (assign) id<WSResponseProtocol> delegate;

@end

The WSResponseProtocol is defined as such:

@protocol WSResponseProtocol <NSObject>

@optional
- (void)responseData:(NSData *)data;
- (void)didFailWithError:(NSError *)error;

@end

To use this, the view controller simply needs to conform to the WSResponseProtocol protocol, to catch the response(s). Making the web service call is done like so:

Proxy *p = [[Proxy alloc] init];
[p searchForSomethingAsync:searchText delegate:self];
[p release];

I can provide more code but the remaining can be assumed. Before calling, I "startAnimating" a UIActivityIndicatorView spinner. But the spinner never spins. If I simply put the NSURLConnection delegate methods directly in the view controller, then the spinner spins. So, it makes me think that my implementation isn't executing asynchronously. Any thoughts/ideas here?

Answer

Matt Long picture Matt Long · Dec 24, 2009

Your approach is reasonable, however, I'm not sure why you are creating your own protocol. This is not necessary. Everything you need to get this implemented is in Apple's documentation on NSURLConnection. If you take the code from that page where the NSURLConnection is instantiated, and make the connection an ivar instead of just creating it as a local variable, you can then compare connection objects in each of the callback methods and respond accordingly. For example, take this code from the docs and change the connection object to an ivar:

// create the request
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.apple.com/"]
                        cachePolicy:NSURLRequestUseProtocolCachePolicy
                    timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
    // Create the NSMutableData that will hold
    // the received data
    // receivedData is declared as a method instance elsewhere
    receivedData=[[NSMutableData data] retain];
} else {
    // inform the user that the download could not be made
}

The variable theConnection is our ivar. Then you can check it like this:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    if (connection == theConnection)
    {
        // do something with the data object.
        [connectionSpecificDataObject appendData:data];
    }
}

You can certainly implement it creating your own protocol as you're suggesting and then call back out to the delegate that conforms to your protocol, but you may be better off just instantiating your object using a success and failure selector that you can check. Something like this:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    if (connection == theConnection)
    {
        if (delegate && [delegate respondsToSelector:successSelector])
            [delegate performSelector:successSelector 
                           withObject:connectionSpecificDataObject];
    }
    [connection release];
}

Where dataDidDownloadSelector is a SEL instance variable that you set when you created your download delegate where all of this code is contained--your Proxy object. Something like this:

Proxy *p = [[Proxy alloc] init];
[p searchForSomethingAsync:searchText 
                  delegate:self 
           successSelector:@selector(didFinishWithData:) 
              failSelector:@selector(didFailWithError:)];

Implement your selectors like this:

- (void)didFinishWithData:(NSData*)data;
{
    // Do something with data
}

- (void)didFailWithError:(NSError*)error
{
    // Do something with error
}

This has become a longer answer than I intended. Let me know if it doesn't make sense and I can try to clarify.

Best regards,