views:

569

answers:

3

I am having difficulty getting my head around memory management in the following segment of code on iPhone SDK 3.1.

// Create array to hold each PersonClass object created below
NSMutableArray *arrayToReturn = [[[NSMutableArray alloc] init] autorelease];

NSArray *arrayOfDictionaries = [self generateDictionaryOfPeople];

[arrayOfDictionaries retain];

for (NSDictionary *dictionary in arrayOfDictionaries) {

    PersonClass *aPerson = [[PersonClass alloc] init];

    for (NSString *key in [dictionary keyEnumerator]) {

        if ([key isEqualToString:[[NSString alloc] initWithString: @"FIRST_NAME"]])
            aPerson.firstName = [dictionary objectForKey:key];

        else if ([key isEqualToString:[[NSString alloc] initWithString: @"LAST_NAME"]])
            aPerson.lastName = [dictionary objectForKey:key];

    }

    // Add the PersonClass object to the arrayToReturn array
    [arrayToReturn addObject: aPerson];

    // Release the PersonClass object
    [aPerson release];

}

return arrayToReturn;

The [self generateDictionaryOfPeople] method returns an array of NSDictionary objects. Each NSDictionary object has two keys "FIRST_NAME" and "LAST_NAME" with a person's first name and last name as the respective data. The code is looping through each dictionary object in the arrayOfDictionaries array and assigning the dictionary data to the relevant property of an aPerson (PersonClass) object. This object is then added to an array which is returned from this method.

When running instruments I am getting a leak for the dictionary objects contained in the arrayOfDictionaries array. The code within the [self generateDictionaryOfPeople] method is calling [dictionaryObject release] on each NSDictionary object as it is created and added to the array, which makes the retain count on the object 1 (as adding the object to the array would make the retain count 2, but then my release message decrements it back to 1).

I assume this leak is because I am never releasing the arrayOfDictionaries array, and thus the NSDictionary objects within the array are never released. If I attempt to release the array at the end of the above segment of code I get a "message sent to deallocated instance" error. I understand why this is occurring, because I am assigning the aPerson object data within a dictionary item (that I am subsequently releasing) but I don't know where else I can release the arrayOfDictionaries array. Any help would be greatly appreciated.

Thanks!

EDIT: Below is the implementation for [self generateDictionaryOfPeople]

- (NSArray *)generateDictionaryOfPeople {

    NSMutableArray *arrayFromDatabase = [[[NSMutableArray alloc] init] autorelease];

    // ** Query the database for data **

    while ( there are rows being returned from the database ) {

        // Declare an NSMutableDictionary object
        NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];

        // Loop through each column for that row
        for ( while there are columns for this row ) {

            columnTitle = title_of_column_from_database
            columnData = data_in_that_column_from_database

            // Add to the dictionary object
            [dictionary setObject:columnData forKey:columnTitle];

            // Release objects
            [columnName release];
            [columnTitle release];

        }

        // Add the NSMutableDictionary object to the array
        [arrayFromDatabase addObject:dictionary];

        // Release objects
        [dictionary release];

    }

    // Return the array
    return arrayFromDatabase;

}
+2  A: 

Here,

    if ([key isEqualToString:[[NSString alloc] initWithString: @"FIRST_NAME"]])
        aPerson.firstName = [dictionary objectForKey:key];

    else if ([key isEqualToString:[[NSString alloc] initWithString: @"LAST_NAME"]])
        aPerson.lastName = [dictionary objectForKey:key];

Replace them with

    if ([key isEqualToString:@"FIRST_NAME"])
        aPerson.firstName = [dictionary objectForKey:key];

    else if ([key isEqualToString:@"LAST_NAME"])
        aPerson.lastName = [dictionary objectForKey:key];

The problem of the leak is you're creating 1 ~ 2 NSString-s per loop without -release-ing them. If you need constant NSString-s, just directly use them.

KennyTM
Thanks for your reply, but I am still getting the original leak due to not releasing the arrayOfDictionaries array. Any ideas on how and when this object should be released?
Skoota
@Mark: You need to show how `-generateDictionaryOfPeople` is defined. By the way, `arrayToReturn` should be `-autorelease`-ed.
KennyTM
@KennyTM: Thanks for your reply. I have tried putting arrayToReturn = [[[NSMutableArray alloc] init] autorelease]. The program loads and shows the table view controller (with the correct data) for less than a second and then crashes with a "message sent to deallocated instance" message at the console. I have updated my question with the implementation for -generateDictionaryOfPeople.
Skoota
@Mark: You need to `-autorelease` your `arrayFromDatabase` in `-generateDictionaryOfPeople`. Your program crashing means you have an erroneous `-release` on the return value of your method. You should never `-release` a returned object *unless* it is named as `create…`, `…copy…` or `init…`. Please review the ownership policy of your program.
KennyTM
@KennyTM: Thanks for your reply Kenny. I think you may have read the updated code above before I put another update into the method. As you will now see, I need to convert the NSMutableArray into an NSArray to be returned, and in doing so I have added an autorelease to the returned NSArray and manually released the NSMutableArray (which is no longer needed). I still don't quite understand what I am doing wrong?
Skoota
@Mark: The code in `-generateDictionaryOfPeople` is fine now, although you don't need to convert your mutable array into an array. Basically, whenever you return an object, you relinquish ownership on that object in that function. Therefore, if the object comes by `-init`, you have to `-release` it. However, immediately `-release`-ing it causes the object to be deallocated and unusable. Therefore `-autorelease` is used to delay the deallocation and give a chance of the caller to just have a peek `-retain` it for later use.
KennyTM
@KennyTM: Thanks for working through this problem with me Kenny. I have modified the code above to reflect your suggestions, however I am still getting a crash (see my reply to @Peter below). Any more ideas?
Skoota
A: 

I am still getting the original leak due to not releasing the arrayOfDictionaries array.

That means you forgot to autorelease it in generateDictionaryOfPeople.

You need to review the memory management rules.

Peter Hosey
@Peter: I have autoreleased the returned array in the generateDictionaryPeople method, although I may have done this incorrectly. I have now updated the question to include that method.
Skoota
OK, so you are autoreleasing that one. You are, however, not releasing the array you put the PersonClass objects into. You should autorelease both of them, following the same rules.
Peter Hosey
@Peter: Thanks for your reply. I have updated the code above as-is at the moment. However, if I autorelease the array which the PersonClass objects are placed into, using NSMutableArray *arrayToReturn = [[[NSMutableArray alloc] init] autorelease], then the program will briefly show the UITableViewController with the relevant data from that array, and then immediately crash with "message sent to deallocated instance". This is a pretty basic program at the moment, and I am not doing anything else fancy with that array, so I don't understand the crash?
Skoota
Whatever is holding on to the array needs to own it, and as long as it owns it, it should retain it. Again, read the memory management rules.
Peter Hosey
@Peter: Thanks for your reply, but I am still having problems after reading the rules and other guides on memory management. I have updated the code with the latest. I have added the various [autorelease] statements and the code works, but I am still getting various leaks. I think this is due to arrayOfDictionaries never being released. However, because objects are taken out of the dictionaries contained in arrayOfDictionaries, and assigned to other variables which are used elsewhere, I have no idea where I release this object? Autorelease on arrayOfDictionaries crashes out as well.
Skoota
...also, strangely enough, if I add [autorelease] to arrayOfDictionaries the view does display correctly, with all the required information, for just less than one second before crashing. By placing debug statements throughout the view controller it seems that everything is executing, but then something happens which causes the program to crash. Removing the [autorelease] on arrayOfDictionaries fixes the crash, but has the obvious implication of resulting in leaks.
Skoota
You need to autorelease the array in `generateDictionaryOfPeople` (which returns an array, not a dictionary?!), and your retain in the first example is extraneous (i.e., a leak). That first example does not hold on to the array past the time the autoreleases come due, so it doesn't need to retain it; moreover, anything that does retain it also needs to release it. Once you fix that, run your app under the debugger and find where the crash occurs.
Peter Hosey
A: 

You are not releasing arrayFromDatabase. (The simplest way to avoid this kind of mistake is to use factories and autorelease as early as possible rather than defer releases manually. In this case, use [NSMutableDictionary dictionary] instead of [[NSMutableDictionary alloc] init].)

Ahruman
@Ahruman: Thanks for your reply. That was actually a small typo in my code - I have now edited the code and you will see on the second last line of the generateDictionaryOfPeople I am releasing arrayFromDatabase. I also tried your suggestion but I still get a crash immediately after the table view controller displays with the message "[CFString release]: message sent to deallocated instance".
Skoota