Managing multiple asynchronous NSURLConnection connections

43,846

Solution 1

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];
}

Solution 2

I have a project where I have two distinct NSURLConnections, and wanted to use the same delegate. What I did was create two properties in my class, one for each connection. Then in the delegate method, I check to see if which connection it is


- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    if (connection == self.savingConnection) {
        [self.savingReturnedData appendData:data];
    }
    else {
        [self.sharingReturnedData appendData:data];
    }
}

This also allows me to cancel a specific connection by name when needed.

Solution 3

Subclassing NSURLConnection to hold the data is clean, less code than some of the other answers, is more flexible, and requires less thought about reference management.

// DataURLConnection.h
#import <Foundation/Foundation.h>
@interface DataURLConnection : NSURLConnection
@property(nonatomic, strong) NSMutableData *data;
@end

// DataURLConnection.m
#import "DataURLConnection.h"
@implementation DataURLConnection
@synthesize data;
@end

Use it as you would NSURLConnection and accumulate the data in its data property:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    ((DataURLConnection *)connection).data = [[NSMutableData alloc] init];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [((DataURLConnection *)connection).data appendData:data];
}

That's it.

If you want to go further you can add a block to serve as a callback with just a couple more lines of code:

// Add to DataURLConnection.h/.m
@property(nonatomic, copy) void (^onComplete)();

Set it like this:

DataURLConnection *con = [[DataURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
con.onComplete = ^{
    [self myMethod:con];
};
[con start];

and invoke it when loading is finished like this:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    ((DataURLConnection *)connection).onComplete();
}

You can extend the block to accept parameters or just pass the DataURLConnection as an argument to the method that needs it within the no-args block as shown

Solution 4

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]];

Solution 5

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.

Share:
43,846
Roman
Author by

Roman

Updated on July 08, 2022

Comments

  • Roman
    Roman almost 2 years

    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.

  • Roman
    Roman over 15 years
    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.
  • Adam Ernst
    Adam Ernst over 15 years
    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).
  • Ben Gottlieb
    Ben Gottlieb over 15 years
    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.
  • Debajit
    Debajit almost 15 years
    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?
  • James Wald
    James Wald over 14 years
    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: stackoverflow.com/questions/1192294/…
  • aldi
    aldi almost 14 years
    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.
  • Matt Gallagher
    Matt Gallagher almost 14 years
    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.
  • roundhill
    roundhill over 13 years
    This helped me a ton. Thanks Matt!
  • Josh Brown
    Josh Brown over 13 years
    This is really useful - thanks! How do I get rid of this warning? "warning: passing argument 1 of 'CFDictionaryAddValue' discards qualifiers from pointer target type"
  • Felipe Sabino
    Felipe Sabino about 13 years
    This really helped as well! And @josh-brown , one reason you might be getting this warning is if you are using CFMutableDictionaryRef as a pointer... I mean CFMutableDictionaryRef *dic = CFDictionaryCreateMutable(... instead of just CFMutableDictionaryRef dic = CFDictionaryCreateMutable(...
  • Matt Lyons-Wood
    Matt Lyons-Wood almost 13 years
    I really like the 'blocks' implementation in ASIHTTPRequest - it's just like Anonymous Inner Types in Java. This beats all the other solutions in terms of code cleanliness and organisation.
  • adit
    adit about 12 years
    be careful this is problematic as it will have race conditions
  • djskinner
    djskinner almost 12 years
    Another method for doing this is shown here: github.com/dbowen/Demiurgic-JSON-RPC. @Matt Gallagher, what are the pros and cons of this method versus your own solution?
  • user501836
    user501836 over 11 years
    on iOS6 can't use the NSURLConnection as the key.
  • jsherk
    jsherk over 11 years
    How do you assign the names (savingConnection and sharingReturnedData) for each connection in the first place?
  • jwarrent
    jwarrent about 11 years
    This is a fantastic answer that worked really well for my case. Very simple and clean!
  • Mike Abdullah
    Mike Abdullah about 11 years
    @adit, no, there is no race condition inherent to this code. You'd have to go pretty far out of your way with the connection creation code to create a race condition
  • Nubzor
    Nubzor about 11 years
    your 'solution' is exactly what the original question is looking to avoid, quoting from above: '... a lot of branching and ugly code begins to formulate ...'
  • abc123
    abc123 almost 11 years
    @adit Why will this lead to a race condition? It's a new concept to me.
  • Shay Aviv
    Shay Aviv over 9 years
    Just wanted to share that for iOS 6.0 and above, you can use a [NSMapTable weakToStrongObjectsMapTable] instead of a CFMutableDictionaryRef and save the hassle. Worked well for me.
  • Kedar Paranjape
    Kedar Paranjape almost 9 years
    Much better encapsulation with respect to one connection.
  • patrickjason91
    patrickjason91 almost 9 years
    I'm new in iOS/Objective-C. I tried using this kind of approach for my NSURLConnections and doesn't work when used in if-else