views:

303

answers:

1

As a disclaimer I'd like to state that I'm fairly new to Objective-C and Cocoa. Currently I'm trying to write a basic application that can POST XML data to a particular endpoint. To achieve this, I've created a ServiceRouter class which uses NSURLConnection to post XML data to a particular URL.

The ServiceRouter class is intended as a base for subclasses which contain webservice-specific XML queries. In the example below, I subclass ServiceRouter to create a ServiceImplementation class.

When it's time to generate and POST the XML, I create an instance of the ServiceImplementation class like so:

[[ServiceImplementation alloc] createServiceSpecificXML];

This all seems to work fine. The issue is that Leaks reports a number of issues. Being fairly inexperienced, I'm not really sure where to start. For the most part, the NSURLConnection code is lifted from Apple's documentation.

Following basic memory management rules, I imagine I will have to release my ServiceImplementation instance at some point. What I'm confused about is how this should be handled given the asynchronous nature of NSURLConnection. Is this a candidate for autorelease?

I'm hoping that someone with more Objective-C/Cocoa experience can look things over and tell me if I'm moving in the right direction.

Here's the ServiceRouter class:

@interface ServiceRouter : NSObject {
    NSMutableData *receivedData;
}

-(void)postXMLToURL:(NSString *)url xml:(NSString *)xmlData;
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
-(void)connectionDidFinishingLoading:(NSURLConnection *)connection;
@end

@implementation ServiceRouter

- (void)postXMLToURL:(NSString *)url xml:(NSString *)xmlData
{
    NSLog(@"Posting XML to URL: %@", url);

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
    [request setHTTPMethod:@"POST"];
    [request setValue:@"application/xml" forHTTPHeaderField:@"Content-type"];
    [request setValue:@"application/xml" forHTTPHeaderField:@"Accept"]; 

    [request setValue:[NSString stringWithFormat:@"%d", [xmlData length]] forHTTPHeaderField:@"Content-length"];
    [request setHTTPBody:[xmlData dataUsingEncoding: NSUTF8StringEncoding]];

    NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
    if(connection) {
        NSLog(@"Connection created");
        receivedData = [[NSMutableData data] retain];
    } else {
        NSLog(@"Issue with connection!");
    }
}

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    if([response respondsToSelector:@selector(statusCode)])
    {
        int statusCode = [((NSHTTPURLResponse *)response) statusCode];
        NSLog(@"HTTP Response code: %i", statusCode);
    }
    NSLog(@"Received response");
    [receivedData setLength:0];
}

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    NSLog(@"Received data: %@", data);
    [receivedData appendData:data];
}

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    [connection release];
    [receivedData release];

    NSLog(@"Connection failed! Error - %@", [error localizedDescription]);
}

-(void)connectionDidFinishingLoading:(NSURLConnection *)connection
{
    NSLog(@"Succeeded! Received %d bytes of data", [receivedData length]);

    [connection release];
    [receivedData release];
}
@end

Here's my ServiceImplementation class:

@interface ServiceImplementation : ServiceRouter {
}
-(void) createServiceSpecificXML;
@end

@implementation ServiceImplementation

-(void) createServiceSpecificXML
{
    NSString *xmlData = @"<example><ignore/></example>";
    [super postXMLToURL:@"http://site.com/endpoint.xml" xml:xmlData];
}

@end
+1  A: 

You never initialize the instance. Merely allocing is not sufficient. You must call init or some other initializer — preferably on the same line as alloc.

From your (working but odd) construct of [[NSMutableData data] retain], I'm going to guess you haven't read a lot of Apple's basic primers. I would recommend at least The Objective-C Programming Language and the memory management guide. Neither is very long, and between these two, I think you'll clear up a lot of your uncertainties.

Chuck
I've now read through the Apple primers you suggested and they were certainly helpful, thank you. I've since updated the code above. As a result, Leaks is no longer reporting issues.For reference, I ensured that the ServiceImplementation instance was correctly initialized, ensured that the NSMutableURLRequest created in postXMLToURL was released at the end of the method, and that my NSURLConnection was autoreleased.Perhaps the only issue I can see is that currently connectionDidFinishingLoading attempts to manually release NSURLConnection. Could this conflict with the call for autorelease?
ndg
It should be noted that I am currently forced to autorelease NSURLConnection because in my experience, there are situations whereby connectionDidFinishLoading never seems to be called. Surely then, the autorelease makes the manual release of NSURLConnection redundant in the connectionDidFinishingLoading and didFailWithError methods?
ndg
From a memory management contract perspective, autorelease and release are equivalent. Both give up your ownership of the object. If you only own an object once, releasing twice is wrong. But more to the point, autorelease is probably not the right tool here. You can't trust an autoreleased object to be around past the current runloop cycle. The NSMutableData object will probably disappear out from under you before you even get to use it.
Chuck