Home > dev, Mobile, Tech > Adding state to NSURLConnection

Adding state to NSURLConnection

When you want to make a data request in Objective-C you typically use NSURLConnection and can either do it synchronously or asynchronously. Obviously the later is the preferred choice so you don’t lock out the thread. Unfortunately, you will run into the problem where you want to make concurrent requests and you’ll find it doesn’t work. This usually happens because you set your class as the delegate and if you kick off another request it’ll step on top of the previous one returning the wrong data. One way around this is to keep a NSMutableDictionary of NSMutableData objects using the NSURLConnection as the key. Unfortunately you can’t do this with NSURLConnection as it doesn’t have a unique identifier that you can use.

Unfortunately you can’t use categories for this since they only allow you to add in methods, and subclassing just to add a field is not the most elegant solution. Luckily for us Objective-C has something nifty called Associative References. Creating an associative reference will let you add data storage (AKA a new iVar) to an existing class without the need to sub-class it.

So enough jabbering, here’s an example:

DataProxy.h

#import <Foundation/Foundation.h>

@interface DataProxy : NSObject {
    NSMutableDictionary *dataStreams;
}

-(void) getData: (NSMutableArray*) arguments withDict: (NSMutableDictionary*) options;

-(NSString*) createUUID;

-(void) removeDataStream: (NSString*) uuid;
    
@property(nonatomic, retain) NSMutableDictionary *dataStreams;

@end

DataProxy.h

#import "DataProxy.h"
#import <objc/runtime.h>

@implementation DataProxy
@synthesize dataStreams;

static char dataInstanceKey;

-(void) getData: (NSMutableArray*) arguments withDict: (NSMutableDictionary*) options{
    if(dataStreams == nil){
        dataStreams = [[NSMutableDictionary alloc] init];
    }
    
    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[options objectForKey:@"url"]]
                                         cachePolicy:NSURLRequestUseProtocolCachePolicy 
                                     timeoutInterval:8.0];
    
    // The caller should set the UA
    [req setValue:[options objectForKey:@"ua"] forHTTPHeaderField:@"User-Agent"];
    
    // Create a UUID to associate with the request
    NSString *uuid = [self createUUID];
    
    // Create & store data keyed off of the UUID
    NSMutableDictionary *ds = [[NSMutableData alloc] initWithCapacity:2048];
    [dataStreams setObject:ds forKey:uuid];
    [ds release];
    
    // Create a URL connection
    NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
    
    // Add an associative reference to the connection to keep the key for the data
    objc_setAssociatedObject(conn, &dataInstanceKey, uuid, OBJC_ASSOCIATION_RETAIN);
    
    // Kick off the connection
    [conn scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [conn start];
    [conn release];
}


#pragma mark -
#pragma mark Private

-(NSString*) createUUID{
    CFUUIDRef theUUID = CFUUIDCreate(NULL);
    CFStringRef str = CFUUIDCreateString(NULL, theUUID);
    CFRelease(theUUID);
    
    return [(NSString*) str autorelease];
}

-(void) removeDataStream: (NSString*) uuid{
    NSMutableData *data = (NSMutableData*) [dataStreams objectForKey:uuid];
    if(data != nil){
        data = nil;
        [dataStreams removeObjectForKey:uuid];
    }
}


-(void) connection: (NSURLConnection *) theConnection didReceiveData: (NSData *) incrementalData{
    // Pull the key out of the connection (we add when the connection is instantiated)
    NSString *uuid = (NSString*) objc_getAssociatedObject(theConnection, &dataInstanceKey);
    
    // Add the data to the correct stream
    [[dataStreams objectForKey:uuid] appendData:incrementalData];
}

-(void) connectionDidFinishLoading: (NSURLConnection*) theConnection{
    // Pull the key out of the connection (we add when the connection is instantiated)
    NSString *uuid = (NSString*) objc_getAssociatedObject(theConnection, &dataInstanceKey);
    
    // Get the correct data
    NSMutableData *data = (NSMutableData*) [dataStreams objectForKey:uuid];
    
    // Convert the result into a string
    NSString *response = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];

    // Do something with the response!    

    [response release];
    
    // Remove the data
    [self removeDataStream:uuid];
}

-(void) connection: (NSURLConnection*) connection didFailWithError: (NSError*) error{
    // Pull the key out of the connection (we add when the connection is instantiated)
    NSString *uuid = (NSString*) objc_getAssociatedObject(connection, &dataInstanceKey);
    
    // Do something with the error
    NSString *error = [error localizedDescription];

    // Remove the data
    [self removeDataStream:uuid];
}

#pragma mark -
#pragma mark Memory Management

- (void)dealloc {
    for(id data in dataStreams){
        if(data != nil){
            data = nil;
        }
    }    
    [dataStreams release], dataStreams = nil;
  
    [super dealloc];
}

@end
About these ads
  1. July 28, 2011 at 1:19 pm
    • November 10, 2011 at 9:15 am

      That’s what I thought — so I’m not sure why its not crashing the app?

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: