PDA

View Full Version : Obj-C: When Do I need to Allocate?




Soulstorm
Apr 26, 2006, 05:52 AM
Hello. I am a newbie to Obj-C, and I have a strong background in C++. Here are some lines of code that I would like to have explained:int main(int argc, char* argv[]){
fraction *a = [[fraction alloc] init];
fraction *b = [[fraction alloc] init];

fraction *result;

[a setTo: 1 over: 3];
[b setTo: 2 over: 5];
[a print];
printf(" + ");
[b print];
printf(" = ");
result = [a add: b];
[result print];
printf("\n");
[result free];

[a print];
printf(" - ");
[b print];
printf(" = ");
result = [a sub: b];
[result print];
printf("\n");
[result free];

[a print];
printf(" * ");
[b print];
printf(" = ");
result = [a mul: b];
[result print];
printf("\n");
[result free];

[a print];
printf(" / ");
[b print];
printf(" = ");
result = [a div: b];
[result print];
printf("\n");
[result free];

return 0;
}
You will probably understand that I have a fraction class declared elsewhere and some methods, which are of no importance to my question.

As you can see, I am pre-allocating the "fraction *a" and "fraction *b" but NOT "fraction *result". Why is that? How come it's not necessary to write
fraction *result = [[fraction alloc]init]to allocate memory for the "result" object?

I know that if I eventually write "fraction *result = [[fraction alloc]init]" it will also be correct, but why doesn't it give me any errors?

NOTE: In the "-(void) setTo: (int) n over: (int) d" function that you see into the "main()" I do not allocate memory for my object

In general, I have a hard time trying to figure out when must I allocate objects using the "alloc" method and when not. I also have a hard time to avoid memory leaks (for example, I know that I shouldn't write "[[a div: b] print]" because an object will be created that I won't be able to free later).

If you still can't figure out what I am saying, here is the entire source (http://users.forthnet.gr/ath/jonmecos/Mainsite/Development/archives/temp/archive.zip).



robbieduncan
Apr 26, 2006, 06:39 AM
It depends on what [a add:b] does. It should be returning a pointer to memory that contains a fraction object, either a or a new object that was created during add, either way the memory is allocated for you in add:. You only need to call alloc/init when you want to create a new object.

Edit: I noticed that you don't think you can write [[a div:b] print]. If your div is written correctly you can. The object returned by div: should be autoreleased. The convention is that all methods that return objects return autoreleased ones apart from init and copy (and their variants).

HexMonkey
Apr 26, 2006, 06:57 AM
In the line fraction *a = [[fraction alloc] init]; you are doing two things. Firstly, you are declaring a variable called a. This basically tells the compiler that you have a variable a that can store a fraction object, but at this stage it doesn't actually hold anything. The second thing it does is initialises a to a new fraction object, so that it's now usable.

In contrast, the line fraction *result; declares a variable called result, but doesn't initialise it. This means that you can use result at a later stage to hold a fraction object, but at this stage it doesn't actually contain one.

So why are they defined differently? Look at the first time you use the a object:
[a setTo: 1 over: 3];
Here, you are altering an object that must already exist. If you hadn't initialised a then you wouldn't be able to do this, because a would just store some random value, not a fraction object.

On the other hand, look at the first time you use the result object:
result = [a add: b];
Here, you are not altering an existing object, but instead you are assigning result to the object returned from the [a add: b]; method. In fact, if you had initialised result like you did for a and b, you'd be creating a memory leak, because the fraction object returned by [a add: b]; would be different to the one returned by [[fraction alloc] init];, so you'd lose the reference to the original memory.

To summarise, if you alter an existing object the first time you use it, you must use alloc to allocate memory for it. However, if you'll be setting the variable to the return value of another method, you don't.

Moving onto memory management, in Objective-C each object has what's called a retain count. When the retain count falls to 0, the object is freed. An object's retain count is increased by sending a retain message to it, while it is decreased if you send it a release or autorelease message. When you allocate memory using alloc or copy, it's retain count is set to 1.

You need to make sure that everything that increases the retain count is matched with something that decreases it, otherwise you'll have memory leaks or your program will try to access freed memory (a common cause of crashes). So for every alloc, copy or retain message, there must be a matching release or autorelease message.

There is also an informal rule in Objective-C that if a method returns an object, it is responsible for freeing it. So when you set result to the fraction object returned from various methods, you do not need to release the memory. This should be done in the methods themselves.

The way this is done is using autorelease. This means that the object will be automatically sent a release message at a later time, after the calling method is done using it. For example, the add method might be implemented like this:

-(fraction*)add:(fraction*)b
{
fraction *result = [[fraction alloc] init];
//Do the calculations to set the value of result
return [result autorelease];
}

In this code, the memory for result is allocated, then before it's returned, it's added to the autorelease pool, so it will be released at a later time. The calling method can then use the returned value with worrying about its memory. In your add method you don't autorelease result as should be done, so just change the last line to what's used in the example above.

In your main method, you use [result free]; after setting the value of result to the return value of a method. Firstly, you should use release not free as explained above, free should not be used in Objective-C. Secondly, if you follow the guidelines above, you can strip these lines altogether.

A few other points:

It's usual to begin class names with a capital letter, so the 'fraction' class would be called 'Fraction'.
You can use NSLog to print to the run log, instead of printf.


I hope this helps. :)

Soulstorm
Apr 26, 2006, 12:42 PM
That sure was quite helpful. I found this example on a book called "Programming in Objective-c" by Kochan. The autorelease pool is explained at a later stage, so that is why the book doesn't mention it.

As for the fraction's 'f' capital letter, the book has it capitalized, but I chose to ignore that for a reason I can't remember... :o

Anyway thanks a lot!