Search code examples
iphoneobjective-ciossoapsudzc

SudzC for iPhone seems to leak 1kB every web service call


Instruments is reporting 2 objects leaked with every call to a Soap Web Service with code generated by SudzC.

I've narrowed it down a simple case, using code calling a free public web service ( http://www.webservicex.net/geoipservice.asmx?WSDL ) and no authentication. Unfortunately, getting a SudzC project running requires a lot of set up, but you can see the generated code by putting the WSDL URL into http://www.sudzc.com/ and choosing 'Objective-C from iOS'.

Leaked Object   #   Address Size    Responsible Library Responsible Frame
Malloc 1.00 KB,     0x5035400   1.00 KB Foundation-[NSCFString appendString:]
NSCFString,     0x4c3d390   32 Bytes    Foundation  -[NSPlaceholderMutableString init]

The call stack for each object is

3 Foundation -[NSCFString appendString:]
4 SoapDemo +[Soap createEnvelope:forNamespace:forParameters:withHeaders:] /Users/user1/Desktop/SRC/SoapDemo/SoapDemo/Soap/Soap.m:50
5 SoapDemo +[Soap createEnvelope:forNamespace:withParameters:withHeaders:] /Users/user1/Desktop/SRC/SoapDemo/SoapDemo/Soap/Soap.m:114
6 SoapDemo -[WSXGeoIPService GetGeoIP:action:IPAddress:] /Users/user1/Desktop/SRC/SoapDemo/SoapDemo/Generated/WSXGeoIPService.m:55
7 SoapDemo -[SoapDemoViewController callWebService] /Users/user1/Desktop/SRC/SoapDemo/SoapDemo/SoapDemoViewController.m:14
8 SoapDemo -[SoapDemoViewController press:] /Users/user1/Desktop/SRC/SoapDemo/SoapDemo/SoapDemoViewController.m:19

The calling code:

- (IBAction)press:(id)sender {
    NSLog(@"pressed");
    [self callWebService];
}

-(void)callWebService {
    WSXGeoIPService* service = [WSXGeoIPService service];
    [service GetGeoIP:self action:@selector(handleResponse:) IPAddress:@"209.85.147.103"];
}

-(void) handleResponse:(id)value{
    NSLog(@"%@", value);
}

The offending line in Soap.m according to instruments in [Soap createEnvelope] looks quite inoffensive (marked with a comment).

+ (NSString*) createEnvelope: (NSString*) method forNamespace: (NSString*) ns forParameters: (NSString*) params withHeaders: (NSDictionary*) headers
{
    NSMutableString* s = [NSMutableString string];
    [s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
    [s appendFormat: @"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns=\"%@\">", ns];
    if(headers != nil && headers.count > 0) {
        [s appendString: @"<soap:Header>"];
        for(id key in [headers allKeys]) {
            if([[headers objectForKey: key] isMemberOfClass: [SoapNil class]]) {
                [s appendFormat: @"<%@ xsi:nil=\"true\"/>", key];
            } else {
                [s appendString:[Soap serializeHeader:headers forKey:key]];
            }
        }
        [s appendString: @"</soap:Header>"];
    }
    [s appendString: @"<soap:Body>"];
    [s appendFormat: @"<%@>%@</%@>", method,[params stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"], method];
    [s appendString: @"</soap:Body>"];
    [s appendString: @"</soap:Envelope>"]; // *** this is reported as causing the leak ***
    return s;
}

I have tried modifying the method (by combining appendString calls, adding some dummy logging etc.) but the leak always occurs in this method.

I can think of a few possibilities:

  • There is a leak elsewhere in the code which Instruments is identifying incorrectly.
  • appendString is being used incorrectly.
  • There is a leak in Apple's NSMutableString appendString method.
  • There is no actual leak.

Can anyone deduce what is happening (or how to find this out)?

I am using iOS SDK 4.3.

Plan B is to convert the project to Automatic Reference Counting...


Solution

  • For those who don't, please follow my changes to fix your leaks, as it perfectly works:

    1. In SoapRequest.h replace variable declaration id postData to NSString* postData
    2. In SoapRequest.h replace its property declaration to @property (copy, nonatomic) NSString* postData (Here, I additionally changed all of the NSString property delcarations to "copy")
    3. In SoapRequest.m, method (SoapRequest*) create: (SoapHandler*) handler action: (SEL) action urlString: (NSString*) urlString soapAction: (NSString*) soapAction postData: (NSString*) postData deserializeTo: (id) deserializeTo change the row request.postData = [postData retain] to request.postData = postData.

    These 3 steps will solve your problems. Hope it will be also implemented in the SudzcC framework as well.