Vyggo
Vyggo

Reputation: 37

NSMutableArray contained objects and memory management

I am studying memory management, and I have made a small program which manages a table object.

Here is my .h:

@interface Person : NSObject {
        NSString *firstName;
        NSString *lastName;
}

@property (readwrite, copy) NSString *firstName; 
@property (readwrite, copy) NSString *lastName;

Here is my .m:

- (IBAction)clic2:(id)sender {
    Person *pers = [[Person alloc]init];
    NSMutableArray *myarray = [[NSMutableArray alloc] init];

    [pers setFirstName:@"FN 1"]; // = pers.firstName
    [pers setLastName:@"LN 1"]; // = pers.lastName
    [myarray addObject:pers];

    [pers setFirstName:@"FN 2"];
    [pers setLastName:@"LN 2"];
    [myarray addObject:pers];

    [pers release];


    for(int i = 0; i < [myarray count]; i++)
        {
        pers = [myarray objectAtIndex:i];
        NSLog(@"%d %@ %@ %@", [myarray objectAtIndex:i], pers, pers.firstName, pers.lastName);
        }

}


- (void)dealloc
{
    [firstName release];
    firstName = nil;

    [lastName release];
    lastName = nil;

    [super dealloc];
}

and this is my NSLog

2011-04-28 21:40:11.568 temp[4456:903] 4495296 <Person: 0x1004497c0> FN 2 LN 2
2011-04-28 21:40:11.571 temp[4456:903] 4495296 <Person: 0x1004497c0> FN 2 LN 2

As you can see, the stored information is the same; it seems that is because myarray always stores the same memory address/content of the person in the array.

I understand from this that when I change the content of pers at the second call the system will also change the information in myarray.

How do I do this so as to not overwrite the data?

Thank you for your answers.


EDIT : This example is for 2 persons but the idea is manage an unbounded number given


EDIT2 : Thank's all for memory management explanations in this case

Upvotes: 2

Views: 689

Answers (5)

more tension
more tension

Reputation: 3352

The array doesn't copy objects added to it, it just retains them. You've got only one Person object for the scope of the -clic2: method. You set the data and add it to the array. The array stores a pointer to the Person object and increments its retain count. But you still only have one Person object, so any change to the first or last name will overwrite the original data you set. What you've got in the array at the end of your -clic2: method is two references to the same Person object.

What you want is something like this:

- ( IBAction )buttonClicked: (id)sender
{
    Person            *person;
    NSMutableArray    *array;
    NSInteger         i, count = 10;

    array = [[ NSMutableArray alloc ] initWithCapacity: count ];
    for ( i = 1; i <= count; i++ ) {
        /* create a new Person object on each iteration through the loop */
        person = [[ Person alloc ] init ];
        /* person has a -retainCount of at least +1 after allocation */

        person.firstName = [ NSString stringWithFormat: @"FN %d", i ];
        person.lastName = [ NSString stringWithFormat: @"LN %d", i ];

        /* adding the object increments the person retainCount to at least +2 */
        [ array addObject: person ];

        /*
         * -release decrements the retainCount, so when we release the array
         * at the end of the method and it sends release to all its objects
         * the memory allocated for the Person objects will be reclaimed.
         */
        [ person release ];
    }

    for ( person in array ) {
        NSLog( @"%@ %@ %@", person, person.firstName, person.lastName );
    }

    /* releasing the array also sends -release to all its objects */
    [ array release ];
}

Upvotes: 3

Sig
Sig

Reputation: 5188

Yep you're right. The NSMutableArray stores the pointer to the Person object and not the object itself/copy of.

If you really want to copy best thing you can do is have the Person class implement the NSCopying protocol.

In .h add the NSCopying declaration, keep the rest the same

@interface Person : NSObject <NSCopying> {
...
}

.m (same as before but append this method)

- (id)copyWithZone:(NSZone *)zone {
    Person *objectCopy = [[Person allocWithZone:zone] init];
    objectCopy.firstName = self.firstName;
    objectCopy.lastName = self.lastName;
    return [objectCopy autorelease];
}

then you can do:

Person *pers = [[Person alloc]init];
NSMutableArray *myarray = [[NSMutableArray alloc] init];

[pers setFirstName:@"FN 1"]; // = pers.firstName
[pers setLastName:@"LN 1"]; // = pers.lastName
[myarray addObject:[pers copy]];

[pers setFirstName:@"FN 2"];
[pers setLastName:@"LN 2"];
[myarray addObject:[pers copy]];

[pers release];

Upvotes: 1

jscs
jscs

Reputation: 64022

This is happening because you only have one Person object.

When you do this:

Person *pers = [[Person alloc] init];

pers points to a block of memory that holds a Person object; it contains a location, or address. It is a "pointer to a Person". When you do this:

[myarray addObject:pers];

the array does not copy any of that block of memory; all it does is copy the location which is indicated by pers.* When you change the values of pers's variables, it changes the memory in that same block. You still have a Person object at the same location, but with different contents. When you add pers to the array again:

[myarray addObject:pers];    // Second time

the array just makes another copy of the pointer, which still only has the same location, and so the array has two copies of the pointer; two addresses that are the same. So when you look at the values at those addresses, you see the same values, because it's really just one object.


*It also indicates that it is interested in keeping that memory around by calling retain on the object at the location.

Upvotes: 2

Tejas
Tejas

Reputation: 651

You are only creating one instance of person and modifying it multiple times (overwriting the values in the process). Adding an object to an array does not create a copy of it. It only maintains a reference to that object in the array.

Here's an example of what your code could do -

NSMutableArray *myarray = [[NSMutableArray alloc] init];

Person *pers1 = [[Person alloc]init];
[pers1 setFirstName:@"FN 1"]; // = pers1.firstName
[pers1 setLastName:@"LN 1"]; // = pers1.lastName
[myarray addObject:pers1];
[pers1 release];

Person *pers2 = [[Person alloc]init];
[pers2 setFirstName:@"FN 2"];
[pers2 setLastName:@"LN 2"];
[myarray addObject:pers2];
[pers2 release];

Upvotes: 3

Chris Wagner
Chris Wagner

Reputation: 21013

In your code

[pers setFirstName:@"FN 1"]; // = pers.firstName
[pers setLastName:@"LN 1"]; // = pers.lastName
[myarray addObject:pers];

[pers setFirstName:@"FN 2"];
[pers setLastName:@"LN 2"];
[myarray addObject:pers];

You are adding the same object to your array twice, the array is storing a pointer to your Person object pers when you setFirstName: and setLastName the second time you are doing it for the same object in memory, your pointer is the same. When you call addObject: it is merely going to increment the retain count of that object and store it's pointer, it is not creating a copy of it.

Create a second Person object named pers2 and do the following.

[pers1 setFirstName:@"FN 1"]; // = pers.firstName
[pers1 setLastName:@"LN 1"]; // = pers.lastName
[myarray addObject:pers1];

[pers2 setFirstName:@"FN 2"];
[pers2 setLastName:@"LN 2"];
[myarray addObject:pers2];

Also don't forget to release myarray when you are finished, currently you are leaking that object.

Another note, you should use the fast enumeration for iterate across your array.

for(Person *person in myarray) {
    // your code
}

Upvotes: 2

Related Questions