views:

1059

answers:

2

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?

+3  A: 

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,

Matt Long
Ah - very good! I see what you're saying - your solution would remove the need of a protocol. Much more straight forward! Thanks for that!I think, though, I'm starting to warm up to the solution Kendal suggested, that is, having synchronous NSURLConnections wrapped in an NSOperation and thrown in a queue. That solution may be a more straight forward solution to the problem of creating a framework of asynchronized web service calls, though my approach + your insight is still viable.
tbehunin
Reverting my comment earlier. This approach is the approach I'm going with vs synchronous NSURLConnections within an NSOperation. NSURLConnection already provides the threading I need without putting it into an NSOperation. Plus, after reading more blogs, synchronous NSURLConnections + authentication doesn't provide as many "hooks" as the asynchronous calls do.
tbehunin
+1  A: 

Your code as is, I think would do nothing - you release the Proxy just after you initialize, so it never even runs.

An approach I like to use is synchronous NSURLConnection calls, inside of an NSOperation which in turn is managed by an NSOperationQueue. I make the object the queue lives in a Singleton, so I just access the instance from anywhere and tell it when I need a new connection started.

Kendall Helmstetter Gelner
Nice! I never thought about that approach of synchronous NSURLConnection calls wrapped in an NSOperation. I think this will be the solution I'm looking for. However with this solution, you need a way to retrieve information. Here's a link to another SO answer to this problem: http://stackoverflow.com/questions/1297733/cocoa-return-information-from-nsoperation
tbehunin
To pass out results I usually store the data result somewhere central via a singleton, and/or pass back the object to anyone interested wrapped in a notification (you can wrap any object in the notification userInfo dictionary).
Kendall Helmstetter Gelner