The correct way to declare, alloc, load, and dealloc an NSMutableArray
Asked Answered
M

3

6

I declare my array in my *.h file:

@interface aViewController: UIViewController
{
   NSMutableArray *anArray;    // You will need to later change this many times.
}
@end

I alloc memory for it my *.m file:

-(void) viewDidLoad
{
   anArray = [[NSMutableArray alloc] init];
}

I click a test-button to load my array (eventually it will need to load DIFFERNT values on each click):

   anArray = [NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil];

And I free it here:

-(void) dealloc
{
   [anArray release];
   [super dealloc];
}

Does that all look ok?

Because it crashes when I later run this code:

   NSLog(@"%d", [anArray count]);

Not sure why a simple "NSLog() and count" would crash everything.


Dirk,

Let me put it this way: I have a HUGE misunderstanding of pointers, arrays, strings, and memory.

I've read everything I can find on it... but (yet) to find a simple, clear, easy-to-understand description.

Can you suggest one? (Hopefully less than 10 pages of reading.) Is there a reference that explains JUST this topic... and from a standpoint of "you have 12 years of coding experience... but NONE that ever dealt with allocating memory or pointers".)

So the variable-name is NOT the way I refer to the object? Then why have it?

I'm used to many other languages that just do this:

myString = "this"
myString = "that"

myInt = 5
myInt = 15

(What could be simpler.)


Looks to me like this would be the easiest way to do this. (And it seems to work. But it is truly correct?)

Don't alloc any memory for my NSMutableArray initially.
Don't re-alloc any memory repeatedly.  (When I change my array's values.)
Don't release it repeatedly. (Before I change my array's values.)
Don't release it when I exit the program.

But:
Always remember to use RETAIN when I initially assign 
(and repeatedly reassign) new values to my anArray variable.

You are not loading your array with anArray = [NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil]; Instead, you are replacing it with a new instance, and worse: with an instance, whose reference is actually owned by some entity you do not control

Wow. So I could have 20 arrays... all called the same name: anArray... and they would be all different? (There's no such thing as a GLOBAL array?)

etc. In order to clear out old values, the method removeAllObject may be handy. There are also mutation methods, which may be used to add multiple values at once.

So... first I have to "remove all the objects"... and then I can call ONE method it re-add all my new values.

anArray = [[NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil] retain]; instead of the alloc/init sequence.

Wow. I thought nothing could be stored in an array without alloc'ing space for it.

If you really intend to replace the entire array, you may want to consider using properties

How would I do that using properties?
What would be the correct way to do something like this:

> anArray = [NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil];
> anArray = [NSMutableArray arrayWithObjects:@"four", @"five", @"six", nil];

Just like I would do:

x = 12;
x = 24;

Wow. I REALLY have totally misunderstand everything about strings, arrays, and memory. I thought the "easy way" was to alloc ONCE... use the mutable array... change it as much as you want... and free it ONCE.

The problem with doing so is that this new array isn't retained,

I would think the old array would be gone... and the new array could be used. (But I guess not.)

Further, you have a memory leak because you never freed the original array.

I was thinking the old array wasn't suppose to be freed... I'm not done with it... I just wish to CHANGE it to contain my new values. (But I guess not.)

but one is to use [anArray release];

I was thinking that would cause me to release the memory I allocated... (but I guess not)... and then I'd have to re-alloc more memory. (But I guess not.)

anArray = [[NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil] retain];

So I have to "retain" it... so it doesn't disappear out from under me? (Not sure why it would. Until I tell it to... in my final dealloc call.)

Another, probably more correct way to fix it would be to use the addObject: or addObjectsFromArray: NSMutableArray methods instead of constantly creating new arrays.

I only want to create ONE array... and just use it as I want to. I never want to ADD to the array. I want to set it to my new values.

Maxson answered 22/2, 2010 at 0:7 Comment(2)
I know this post is ages old in internet time, but I'd like to clarify that in Cocoa development we are basically NEVER really manipulating datastructures themselves, but rather pointers to them. Keep this in mind at all times, and keep in in mind that you need to be very careful with pointer assignment.Vaca
I wish I could give you more than one up vote - I am having the exact same problems - Objective C is beyond counter intuitive.Bowery
M
11

You are not loading your array with

anArray = [NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil];

Instead, you are replacing it with a new instance, and worse: with an instance, whose reference is actually owned by some entity you do not control (most likely, an NSAutoreleasePool.) The reference you properly owned, the one created by

[[NSMutableArray alloc] init]

is lost and will be leaked.

Instead of replacing the entire array reference, mutate the one you have at your disposal already, using for example addObject: like

[anArray addObject: @"one"]
[anArray addObject: @"two"]

etc. In order to clear out old values, the method removeAllObject may be handy. There are also mutation methods, which may be used to add multiple values at once.

Alternatively, you can allocate the array using the construction method you already use, but be careful to retain it. In viewDidLoad do

anArray = [[NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil] retain];

instead of the alloc/init sequence.

If you really intend to replace the entire array, you may want to consider using properties instead of doing the reference counting manually.

Miserable answered 22/2, 2010 at 0:14 Comment(0)
S
4

The problem is anArray = [NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil]; This replaces the array you initially created. The problem with doing so is that this new array isn't retained, so you lose it as soon as the method returns. Further, you have a memory leak because you never freed the original array.

There are several ways to fix this, but one is to use [anArray release]; anArray = [[NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil] retain]; instead.

Another, probably more correct way to fix it would be to use the addObject: or addObjectsFromArray: NSMutableArray methods instead of constantly creating new arrays.

Scholarship answered 22/2, 2010 at 0:15 Comment(0)
S
3

Remember this simple memory rule: only release objects you own. You own objects only when you create them using:

  • init (this method creates a new object with a retain count of 1)
  • new (this method is the same as using alloc and init)
  • copy (this method creates a new object with a retain count of 1 and with the contents of the receiver of the method)
  • retain (this method increases the retain count with 1)

The system deallocates automatically objects with a retain count of zero. You have to release every object you own after you're done with it. If you release too soon, you get a dangerous situation. If you don't release your object when you're done with it, you get a leak.

A pointer is a special object that refers to some object in memory. It's basically a memory address (and some other data). If you want to use an object, you have to allocate memory for it and then initialize. You assign (by using the = sign) to a pointer.

string = [[NSString alloc] init];

string now has a retain count of one. But because NSString is an immutable object, it can't be changed after initialization. One way to assign string a value is by doing it while initializing it.

string = [[NSString alloc] initWithString: @"Hello, World!"];

If you need to change string regularly, you can use another class that is mutable: NSMutableString. But still, the only way to change them is in a message.

string = [[NSMutableString alloc] initWithString: @"Initial string"];
[string setString: @"Modified string"];

Just to note, this following code is wrong and results in a memory leak.

string = [[NSMutableString alloc] initWithString: @"Initial string"];
string = @"Modified string";

In the first line, string is assigned to a newly created object. In the second one, string is assigned to another string. You lose the reference to the newly created object and you get a leak: you can't release an object you don't have a reference to.

You don't get a problem when you do this with integers (int type), because it's a "native" object. The Objective-C Runtime associates those data types directly with their value, not a pointer.

int1 = 4;
int1 = 5;

Note 1. Don't forget to always tell the runtime what type your pointer is. If you want to use string, you have to define it first in your header (if it's public) or implementation (if you want it hidden from other methods). Then you can use the name freely.

NSString *string;

The star tells the runtime it's a pointer. For native types, there no pointer, so you get this.

int int1;

Note 2. Arrays (and dictionaries, etc.) behave all like NSString in that they have both immutable and mutable variants.

Note 3. Most classes have special methods that allocate, initialize and autorelease all at once.

NSArray *myArray = [NSArray arrayWithObjects: @"Butter", @"Milk", @"Honey", nil];

An autoreleased object doesn't need manual release as the system will release it for you after a while. It's very handy in situations where you need an object only for the duration of the method or between methods, not as a permanent part of an object. When you autorelease some object on the main thread (in an app with a GUI), it will be released when the event loop finishes the current loop (it finishes when some event is processed). Read more about Autorelease Pools in Apple's docs.

I hope I help everyone who doesn't understand well what pointers are. I myself had problems for a few months until I experimented heavily. :-)

Sanity answered 22/2, 2010 at 0:7 Comment(2)
Do you need to loop through all the elements in the array and call the release on each before?Swarthy
@Swarthy If you’re using ARC (which is the default in Xcode), you never use release altogether. ;-)Sanity

© 2022 - 2024 — McMap. All rights reserved.