Reputation: 37
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
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
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
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
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
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