views:

1642

answers:

3

This is with reference to the StackOverflow question Managing multiple asynchronous NSURLConnection connections

I have multiple asynchronous HTTP requests being made at the same time. All these use the same NSURLConnection delegate functions. (The receivedData object is different for each connection as specified in the other question above. In the delegate, I parse the receivedDate object, and do additional operations on those parsed strings)

Everything works fine for me so far, but I'm not sure if I need to do anything to ensure correct “multithreaded” behavior.

  • Is it possible that more than two connections will use the delegate at the same time? (I would think yes)
  • If yes, how is it resolved? (Does Cocoa do this automatically?)
  • Do I need to have additional checks in place to ensure that each request is handled “correctly”?
+4  A: 

Assuming you're launching all of the (asynchronous) connections on a single thread, then the delegate messages will all get posted in that thread's run loop. Therefore the delegate only needs to be able to deal with one message being handled at once; the run loop will hand one message off at a time. This means that while the order of the delegate messages is unknown and the next message could come from any connection object, there will be no concurrent execution of your delegate methods.

However, were you actually trying to use the same delegate object across multiple threads, rather than just using the asynchronous nature of the API, then you would need to deal with concurrent delegate methods.

Graham Lee
A: 

Yes it's possible to have multiple connections. the notification object contains a pointer to the NSURLConnection that triggered the notification.

Internally I guess NSURLConnection listens to a socket and does something like this when it has data ready.

[your_delegate 
    performSelectorOnMainThread:@selector(connectionCallback:) 
    withObject:self 
    waitUntilDone:NO];

so you don't have to worry about it being multithreaded, NSURLConnection will take care of this. For simplicity I have written self, in the real world a NSNotification object is given.

You shouldn't have to do any checks related to multithreading.

neoneye
+5  A: 

I enhanced the Three20 library to implement asynchronous connections across multiple threads in order to fetch data even if the user was playing with the UI. After many hours of chasing down random memory leaks that were detected within the CFNetwork framework I finally root caused the issue. I was occasionally losing track of responses and data.

Any data structures which are accessed by multiple threads must be protected by an appropriate lock. If you are not using locks to access shared data structures in a mutually exclusive manner then you are not thread safe. See the "Using Locks" section of Apple's Threading Programming Guide.

The best solution is to subclass NSURLConnection and add instance variables to store its associated response and response data. In each connection delegate method you then cast the NSURLConnection to your subclass and access those instance variables. This is guaranteed to be mutually exclusive because every connection will be bundled with its own response and data. I highly recommend trying this since it is the cleanest solution. Here's the code from my implementation:

@interface TTURLConnection : NSURLConnection {
 NSHTTPURLResponse* _response;
 NSMutableData* _responseData;
}

@property(nonatomic,retain) NSHTTPURLResponse* response;
@property(nonatomic,retain) NSMutableData* responseData;

@end

@implementation TTURLConnection

@synthesize response = _response, responseData = _responseData;

- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate {
 NSAssert(self != nil, @"self is nil!");

 // Initialize the ivars before initializing with the request
    // because the connection is asynchronous and may start
    // calling the delegates before we even return from this
    // function.

 self.response = nil;
 self.responseData = nil;

 self = [super initWithRequest:request delegate:delegate];
 return self;
}

- (void)dealloc {
 [self.response release];
 [self.responseData release];

 [super dealloc];
}

@end

/////////////////////////////////////////////////////////////////
////// NSURLConnectionDelegate

- (void)connection:(NSURLConnection*)connection
didReceiveResponse:(NSHTTPURLResponse*)response {
 TTURLConnection* ttConnection = (TTURLConnection*)connection;
 ttConnection.response = response;
 ttConnection.responseData = [NSMutableData
                                 dataWithCapacity:contentLength];
}

- (void)connection:(NSURLConnection*)connection
    didReceiveData:(NSData*)data {
 TTURLConnection* ttConnection = (TTURLConnection*)connection;
 [ttConnection.responseData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
 TTURLConnection* ttConnection = (TTURLConnection*)connection;

    if (ttConnection.response.statusCode == 200) {
        // Connection success
    }
}

- (void)connection:(NSURLConnection *)connection
  didFailWithError:(NSError *)error {  
    TTURLConnection* ttConnection = (TTURLConnection*)connection;
    // Handle the error
}
James Wald