Jim
Jim

Reputation: 5960

Instruments Pointing to Leak in Framework -- Found Leak was Somewhere Else (Why?)

I'm becoming more familiar with Instruments for debugging, and it's much easier to use than it initially appeared. This problem was posted so I could understand why it was pointing to one line of code as the source of my leak, and what the logical process would be to resolve it.

The answer may benefit someone else trying to use instruments. I haven't seen a lot of details on how to use it. (There is the Instruments User's Guide, which is a good start, but that's about it.)

In this case, Instruments was pointing to a repeatable leak at the line returning a JSONValue (type NSDictionary) from the SBJSON framework. I tried various things to isolate the problem (see below), and in every case, Instruments still pointed to the line returning the JSON dictionary object.

One more thing that I tried was the NSJSONSerializer available with iOS 5. Instruments again pointed to the same line. Clearly, Instruments was misleading me. (Why? What can I do to avoid/improve this?)

To make a long story short, the problem was not where Instruments was pointing, but it was within the instance that contained that line. In this case, there were two properties, whose values were derived from the deserialized JSON dictionary, that were not being released.

I arrived at this conclusion through trial and error, commenting out code and replacing the returned values with literal strings. (I'm sorry if I put a fright in you, Stig!)


An earlier update

Here is a revision of my post. I believe I have narrowed the problem down to the failure of an SBJSON returned string not being autoreleased. I'd like some other thoughts on whether my process of elimination makes sense, what conclusion should I make, and how to resolve this.

The original question is down below. I have focused on this code:

    NSString *resultsGeocodeLiteralString = @"{... the rest of the string ...}";        
    NSAutoreleasePool *aPool = [[NSAutoreleasePool alloc] init];
/*1*/     NSDictionary *dico = [resultsGeocodeLiteralString JSONValue]; // <-- Instruments still points here.
/*2a*/    self.resultsGeoCode = [[NSDictionary alloc] initWithDictionary:dico copyItems:YES];
/*2b*/    [self.resultsGeoCode release];
    [aPool release];

Here I use a literal string resultsGeocodeLiteralString that is converted to an NSDictionary using the SBJSON JSONValue method. I tested with the literal string to divorce the problem from the original input argument and its memory management.

Ignoring the autorelease pool for a moment, I tried this code, deep copying the JSONValue result into a the property self.resultsGeoCode.

Instruments pointed to the leak being at the line calling the JSONValue method.

This makes me think something is wrong with autoreleasing. So I wrapped this code in a short autorelease pool. This had no effect on the results. Instruments showed the same leak, pointing to the same line.


Original Question Below:

Instruments is pointing to this code as a source of a couple of leaks. I have run out of ideas on how to resolve this. This method is in a object of type Geocoder

First, here is the code. Instruments is pointing the the line marked /1/. I tried deep copying the NSDictionary returned as a JSONValue (/2a/ and /2b/), just to distance the problem from the returned object, but that didn't make any difference.

- (NSString *) processResults:(NSString *) resultsGeoCodeString {

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

/*1*/     self.resultsGeoCode = [resultsGeoCodeString JSONValue];
/*2a*/    self.resultsGeoCode = [[NSDictionary alloc] initWithDictionary:self.resultsGeoCode copyItems:YES];
/*2b*/    [self.resultsGeoCode release]; // It's retained twwice in the line above.

/* copy a stirng */
    self.previousStatusCode = [self.resultsGeoCode objectForKey:@"status"];

    if ([self.previousStatusCode isEqualToString:@"OK"] == YES) {

/* Break it down. Results might be a single object or an array of objects. If it's an array, just take the first one. */
        NSArray *address_components;
        NSDictionary *geom;
        if ([[self.resultsGeoCode objectForKey:@"results"] isKindOfClass:[NSArray class]]) {
            address_components = [[(NSArray *)[self.resultsGeoCode objectForKey:@"results"] objectAtIndex:0] objectForKey:@"address_components"];
            geom = [[[self.resultsGeoCode objectForKey:@"results"] objectAtIndex:0] objectForKey:@"geometry"];
        }
        else {
            address_components = [[[self.resultsGeoCode objectForKey:@"results"] objectAtIndex:0] objectForKey:@"address_components"];
            geom = [[self.resultsGeoCode objectForKey:@"results"] objectForKey:@"geometry"];
        }

/* deep copy the geometry */
        self.geometry = [[NSDictionary alloc] initWithDictionary:geom copyItems:YES];
        [self.geometry release]; // it is retained twice in the line above

/* copy a string */
        self.formattedAddress = [[[self.resultsGeoCode objectForKey:@"results"] objectAtIndex:0] objectForKey:@"formatted_address"];

/* copy the strings from specific types */
        NSArray *typesArray;
        for (NSDictionary *address_component in address_components) {
            if ([[address_component objectForKey:@"types"] isKindOfClass:[NSArray class]])
                typesArray = [address_component objectForKey:@"types"];
            else
                typesArray = [NSArray arrayWithObjects:[address_component objectForKey:@"types"], nil];

            for (NSString *componentType in typesArray) {

                if ([componentType isEqualToString:@"locality"]) 
                    self.city = [address_component objectForKey:@"long_name"];

                else if ([componentType isEqualToString:@"administrative_area_level_1"]) 
                    self.region = [address_component objectForKey:@"long_name"];

                else if ([componentType isEqualToString:@"country"]) {
                    self.country = [address_component objectForKey:@"long_name"];
                    self.countryCode = [address_component objectForKey:@"short_name"];
                }
                else if ([componentType isEqualToString:@"postal_code"]) 
                    self.postalCode = [address_component objectForKey:@"long_name"];

                else if ([componentType isEqualToString:@"street_number"]) 
                    self.streetNumber = [address_component objectForKey:@"long_name"];

                else if ([componentType isEqualToString:@"route"]) 
                    self.route = [address_component objectForKey:@"long_name"];

            }
        }
    }

    [pool release];

/* a retained property set to nil -- not needed anymore */
    self.resultsGeoCode = nil;

/* return a string */    
    return self.previousStatusCode;
}

All NSString properties have the copy attribute. All NSDictionary properties have a retain attribute. You can see I do a deep copy of any dictionary items.

Here is the dealloc method:

- (void) dealloc {
/* NSString properties with copy attribute */
    self.streetNumber = nil;
    self.route = nil;
    self.city = nil;
    self.region = nil;
    self.country = nil;
    self.postalCode = nil;
    self.previousStatusCode = nil;
    self.region = nil;

/* I have tried it with and without these releases  of the dictionary properties */
    if (location_) [location_ release];
    if (geometry_) [geometry_ release];
    if (regionGeometries_) [regionGeometries_ release];
    if (resultsGeoCode_) [resultsGeoCode_ release];

/* I would have thought these would be sufficient to release the retained properties */
//    self.location = nil;
//    self.geometry = nil;
//    self.regionGeometries = nil;
//    self.resultsGeoCode = nil;

    [super dealloc];
}

Instruments says the leaks object is an NSCFString and the responsible frame (I don't know what that is) is -[NSPlaceholderString initWithBytes:length:encoding:].

I hope there's not too much code here to look through, but I stumped. Also, I'm curious if there are any obvious misunderstandings that show up as to how property setters behave. (All setters and getters are synthesized.)

Upvotes: 0

Views: 615

Answers (1)

0xDE4E15B
0xDE4E15B

Reputation: 1294

This is not 100% answer, because we do not know what happens next and what happened before.

I am not sure, but there are some things you do not have to do:
Firstly:

/*2a*/    self.resultsGeoCode = [[NSDictionary alloc] initWithDictionary:[resultsGeoCodeString JSONValue] copyItems:YES];

instead of those 3 lines. If you do not need the argument of the function resultsGeoCodeString then you can release it.

Secondly:

self.geometry = [[NSDictionary alloc] initWithDictionary:geom copyItems:YES];
[self.geometry release];

Why are you releasing it right after you've allocated this object? Release it after you've done with this object. The same as the previous point.

And before the [pool release]; add line [self.resultsGeoCode release]; self.resultsGeoCode = nil; or earlier.

I suggest you using ARC instead of manual retain counting.

Upvotes: 1

Related Questions