views:

6850

answers:

10

Hey folks,

I have a ton of repeating code in my class that looks like the following:

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
                                                              delegate:self];

The problem with asynchronous requests is when you have various requests going off, and you have a delegate assigned to treat them all as one entity, a lot of branching and ugly code begins to formulate going:

What kind of data are we getting back? If it contains this, do that, else do other. It would be useful I think to be able to tag these asynchronous requests, kind of like you're able to tag views with IDs.

I was curious what strategy is most efficient for managing a class that handles multiple asynchronous requests.

+2  A: 

I usually create an array of dictionaries. Each dictionary has a bit of identifying information, an NSMutableData object to store the response, and the connection itself. When a connection delegate method fires, I look up the connection's dictionary and handle it accordingly.

Ben Gottlieb
Ben, would it be okay to ask you for a piece of sample code? I'm trying to envision how you're doing it, but it's not all there.
Coocoo4Cocoa
In particular Ben, how do you look up the dictionary? You can't have a dictionary of dictionaries since NSURLConnection doesn't implement NSCopying (so it can't be used as a key).
Adam Ernst
Matt has an excellent solution below using CFMutableDictionary, but I use an array of dictionaries. A lookup requires an iteration. Its not the most efficient, but it's fast enough.
Ben Gottlieb
+26  A: 

I track responses in an CFMutableDictionaryRef keyed by the NSURLConnection associated with it. i.e.:

connectionToInfoMapping =
    CFDictionaryCreateMutable(
        kCFAllocatorDefault,
        0,
        &kCFTypeDictionaryKeyCallBacks,
        &kCFTypeDictionaryValueCallBacks);

It may seem odd to use this instead of NSMutableDictionary but I do it because this CFDictionary only retains its keys (the NSURLConnection) whereas NSDictionary copies its keys (and NSURLConnection doesn't support copying).

Once that's done:

CFDictionaryAddValue(
    connectionToInfoMapping,
    connection,
    [NSMutableDictionary
        dictionaryWithObject:[NSMutableData data]
        forKey:@"receivedData"]);

and now I have an "info" dictionary of data for each connection that I can use to track information about the connection and the "info" dictionary already contains a mutable data object that I can use to store the reply data as it comes in.

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    NSMutableDictionary *connectionInfo =
        CFDictionaryGetValue(connectionToInfoMapping, connection);
    [[connectionInfo objectForKey:@"receivedData"] appendData:data];
}
Matt Gallagher
Since it is possible that two or more asynchronous connections may enter the delegate methods at a time, is there anything specific that one would need to do to ensure correct behavior?
Nocturne
(I have create a new question here asking this: http://stackoverflow.com/questions/1192294/cocoa-any-checks-required-for-multiple-asynchronous-nsurlconnections )
Nocturne
This is not thread safe if the delegate is being called from multiple threads. You must use mutual exclusion locks to protect the data structures. A better solution is subclassing NSURLConnection and adding response and data references as instance variables. I am providing a more detailed answer explaining this at Nocturne's question: http://stackoverflow.com/questions/1192294/cocoa-any-checks-required-for-multiple-asynchronous-nsurlconnections
James Wald
As stated before, this is not thread safe. I'd rather prefer to solve this issue by using NSOperation subclasses and NSOperationQueue, which in addion allows you to manage the number of ongoing concurrent requests at one time. Each of these operations may handle their NSURLConnection callbacks and inform their individual delegates about progress and results.
aldi
Aldi... it *is* thread safe provided you start all connections from the same thread (which you can do easily by invoking your start connection method using performSelector:onThread:withObject:waitUntilDone:).Putting all connections in an NSOperationQueue has different problems if you try to start more connections than the max concurrent operations of the queue (operations get queued instead of running concurrently). NSOperationQueue works well for CPU bound operations but for network bound operations, you're better off using an approach that doesn't use a fixed size thread pool.
Matt Gallagher
This helped me a ton. Thanks Matt!
roundhill
A: 

One option is just to subclass NSURLConnection yourself and add a -tag or similar method. The design of NSURLConnection is intentionally very bare bones so this is perfectly acceptable.

Or perhaps you could create a MyURLConnectionController class that is responsible for creating and collecting a connection's data. It would then only have to inform your main controller object once loading is finished.

Mike Abdullah
+3  A: 

One approach I've taken is to not use the same object as the delegate for each connection. Instead, I create a new instance of my parsing class for each connection that is fired off and set the delegate to that instance.

Brad Smith
+1  A: 

Try my custom class, MultipleDownload, which handles all these for you.

leonho
+2  A: 

I had a similar problem with one of my applications. I created a class to fix it which you may find useful. It allows you to create a connection with an identifier, it will collect all data for that connection and return it to you once it has finished loading. You can find it at:

.h http://www.codecollector.net/view/994/M3EncapsulatedURLConnectionh

.m http://www.codecollector.net/view/995/M3EncapsulatedURLConnectionm

Your links are broken?
Spanky
A: 

As pointed out by other answers, you should store connectionInfo somewhere and look up them by connection.

The most natural datatype for this is NSMutableDictionary, but it cannot accept NSURLConnection as keys as connections are non copyable.

Another option for using NSURLConnections as keys in NSMutableDictionary is using NSValue valueWithNonretainedObject]:

NSMutableDictionary* dict = [NSMutableDictionary dictionary];
/* store: */
[dict setObject:connInfo forKey:[NSValue valueWithNonretainedObject:aConnection]];
/* lookup: */
[dict objectForKey:connInfo];
mfazekas
A: 

int id = urlConnection;

use the pointer as key, is it okay?

Gung Shi Jie
You should ask this as a separate question with more context. It's not clear what you're asking, and you shouldn't ask it in an answer on another question anyway.
Peter Hosey
I am suggesting not asking.NSURLConnection *connection, see this is a unique pointer.Thus it can be used as the key in NSDictionary.
Gung Shi Jie
OK, int is not an NSObject thus can not be use as a key. But NSNumber can.
Gung Shi Jie
A: 

I like ASIHTTPRequest.

Rui Pacheco
+1  A: 

THIS IS NOT A NEW ANSWER. PLEASE LET ME SHOW YOU HOW I DID

To distinguish different NSURLConnection within same class's delegate methods, I use NSMutableDictionary, to set and remove the NSURLConnection, using its (NSString *)description as key. The object I chose for setObject:forKey is the unique URL that is used for initiating NSURLRequest, the NSURLConnection uses. Once set NSURLConnection is evaluated at -(void)connectionDidFinishLoading:(NSURLConnection *)connection, it can be removed from the dictionary.

// This variable must be able to be referenced from - (void)connectionDidFinishLoading:(NSURLConnection *)connection
NSMutableDictionary *connDictGET = [[NSMutableDictionary alloc] init];
//...//

// You can use any object that can be referenced from - (void)connectionDidFinishLoading:(NSURLConnection *)connection
[connDictGET setObject:anyObjectThatCanBeReferencedFrom forKey:[aConnectionInstanceJustInitiated description]];
//...//

// At the delegate method, evaluate if the passed connection is the specific one which needs to be handled differently
if ([[connDictGET objectForKey:[connection description]] isEqual:anyObjectThatCanBeReferencedFrom]) {
// Do specific work for connection //

}
//...//

// When the connection is no longer needed, use (NSString *)description as key to remove object
[connDictGET removeObjectForKey:[connection description]];
Peter SHINe 신동혁