dealloc method is not invoked when set an object to nil - objective-c

I have a question.
I first created an object which extends NSObject, I provided overrides for the description and dealloc methods. Here's my Employee.m file:
#implementation Employee
.....
-(NSString *)description
{
return [NSString stringWithFormat:#"Employ ID: %d has $%d value of assets", [self employeeID], [self valueOfAssets]];
}
-(void)dealloc
{
NSLog(#"deallocating.. %#", self);
[super dealloc];
}
In my main.m, I first created an NSMutableArray to hold a list of Employee objects:
NSMutableArray *employees = [[NSMutableArray alloc] init];
for (int i =0; i< 10; i++)
{
// Create an instance of Employee
Employee *person = [[Employee alloc] init];
// Give the instance varaible interesting values
[person setEmployeeID:i];
[employees addObject: person];
}
and at the end I set employees to nil
employees = nil;
I expected the dealloc method of each Employee object to be called and I would see some logs like:
deallocating.. Employ ID 0 has value.....
deallocating.. Employ ID 2 has value.....
....
However, I didn't see any logs and if I set a breakpoint on the dealloc method, the breakpoint is never hit.
Any thoughts?

A couple of observations:
person = nil does not release an object in non-ARC code. It will in ARC code (at least if it's strong).
In ARC, local objects will be released for you automatically when they fall out of scope. In non-ARC, objects falling out of scope will not be released for you (and if you don't have other references to those objects elsewhere, you'll end up with a leak).
Adding an item to a mutable array will increase the retain count of the item, so even if you include a release in your non-ARC code, the object won't be released until the retain count drops to zero (accomplished by not only releasing the person objects after you add them to the array, but also removing them from the array.
Thus, given that this is non-ARC code, it could be something like:
- (void)testInNonArcCode
{
NSMutableArray *employees = [[NSMutableArray alloc] init]; // employees retain count = +1
for (int i =0; i< 10; i++)
{
//create an instance of Employee
Employee *person = [[Employee alloc] init]; // person retain count = +1
//Give the instance varaible interesting values
[person setEmployeeID:i];
[employees addObject: person]; // person retain count = +2
[person release]; // person retain count = +1 (YOU REALLY WANT TO DO THIS OR ELSE OR NON-ARC PROGRAM WILL LEAK)
// person = nil; // this does nothing, except clears the local var that's limited to the for loop scope ... it does nothing to reduce the retain count or improve memory management in non-ARC code, thus I have commented it out
}
// do whatever you want
[employees removeAllObjects]; // this will remove all of the person objects and they will have their respective retain counts reduced to 0, and therefore the Employee objects will be released
[employees release]; // employees array's own retain count reduced to zero (and will now be dealloced, itself)
}
In ARC code:
- (void)testInArcCode
{
NSMutableArray *employees = [[NSMutableArray alloc] init]; // employees retain count = +1
for (int i =0; i< 10; i++)
{
//create an instance of Employee
Employee *person = [[Employee alloc] init]; // person retain count = +1
//Give the instance varaible interesting values
[person setEmployeeID:i];
[employees addObject: person]; // person retain count = +2
// person = nil; // this would reduce person retain count to +1 (but unnecessary in ARC because when person falls out of scope, it will have it's retain count automatically reduced)
}
// do whatever you want
[employees removeAllObjects]; // this will remove all of the person objects and they will have their respective retain counts reduced to 0, and therefore will be released
// [employees release]; // not permitted in ARC
// employees = nil; // this would effectively release employees, but again, not needed, because when it falls out of scope, it will be released anyway
}

The proper way of freeing objects is to do
[employees release];
Setting it to nil will not release the memory.

By virtue of you being allowed to call [super dealloc], I can assume that you are not using Automatic Reference Counting. This means that you need to explicitly pair every alloc you write with a balancing release call. For you, when you make the array nil, you essentially leaked all of the memory for the employees. You need to loop over the array again to release them all, or better yet since you are learning... Start as soon as possible writing ARC code.
It may be important to note that ARC was created for exactly this kind of situation; it makes sense to our brains, and now it can be a reality if you use the latest tools.

Related

Why does the reference count when retrieving object from container not increase?

I have created a little test project to try to resolve a problem I am having in my main project. I've noticed that when retrieving an object from a container the reference count dosen't increment.
I am confused why this is not the case?
For example this code will not increase the reference count of the hereDoggy object:
//Retrieve the dog, why does this not increment the reference count?
Dog* hereDoggy = [cont1 objectAtIndex:0];
Below is the full example:
-(void)doZombieProblem
{
NSMutableArray* cont1 = [NSMutableArray array];
NSMutableArray* cont2 = [NSMutableArray array];
NSMutableArray* cont3 = nil;
//Create the dog pointer
Dog* doggy = [[Dog alloc] initWithName:#"Bernard"];
//Add to container1
[cont1 addObject:doggy];
//Release the dog pointer
[doggy release];
while ([cont1 count] > 0)
{
//Retrieve the dog, why does this not increment the reference count?
Dog* hereDoggy = [cont1 objectAtIndex:0];
//Add it to cont2
[cont2 addObject:hereDoggy];
//Remove it from cont1.
[cont1 removeObjectAtIndex:0];
//No need to release as we haven't increased the reference count.
//[hereDoggy release];
}
//I should be able to retrieve the dog here from cont2.
Dog* bernard = [cont2 objectAtIndex:0];
//No need to release as we haven't increased the reference count.
//[bernard release];
}
In this case, if you want to increase the retain count for your object you need to send a retain (or a copy) message.
As a rule of thumb
You need always to balance your retains (or copyies) with your releases. If you don't do it you can have memory leaks. Otherwise switch to the ARC feature to avoid the code amount to write and simplify your life.
Here a useful link to understand how Memory Management works.
MemoryMgmt
I commented your code to understand what is going on:
// the object referenced by doggy has a retain count of 1
Dog* doggy = [[Dog alloc] initWithName:#"Bernard"];
// now the retain count is 2 since you added to a container class like NSArray
[cont1 addObject:doggy];
// now the retain count is 1
[doggy release];
Then, within the while statement:
// the retain count still remains 1
Dog* hereDoggy = [cont1 objectAtIndex:0];
// the retain count increases to 2
[cont2 addObject:hereDoggy];
// the retain count goes to 1
[cont1 removeObjectAtIndex:0];
Since, the object is maintained alive by cont2 you are able to access it.
If you do [cont2 removeObjectAtIndex:0]; the retain count reaches 0 and the object is deallocated automatically.
It's your responsibility as the user of the object to manage it's retain count. This is because only you, the consumer, know when you are done with it. That's why just calling [cont1 objectAtIndex:0] doesn't increment it. NSArray has no clue what you have planned with the object it returns.
Think of retain count to indicate the number of things owning something. When it's 0, no one owns it, so let it be garbage collected. If it's 1, then only 1 thing needs it/owns it (and on up).
When you call [cont1 addObject:doggy] NSMutableArray will absolutely increment the retain count on it (behind the scenes), just like when you call [cont1 removeObjectAtIndex:0] NSMutableArray will decrement the retain count on it.
Now, if you need hereDoggy for any period of time, just call retain on it yourself, and then release where appropriate.

Is NSMutableArray recursive in releasing it's items?

If I have a 2 dimensional NSMutableArray eg,
board = [[NSMutableArray alloc] initWithCapacity:boardHeight];
for (int y = 0; y < boardHeight; y++) {
NSMutableArray *row = [[NSMutableArray alloc] initWithCapacity:boardWidth];
for (int x = 0; x < boardWidth; x++) {
[row insertObject:#"A string"];
}
[board insertObject:row atIndex:y];
[row release];
}
and I do
[board release];
Does that recursively release the array? Or do I have to manually go into the array and release each row?
If it does, and the object inserted into each row were a custom object, is there anything special I have to think about when writing the dealloc method for the custom object?
Everything will just work fine. The array will retain the objects when they are added and released when removed or the array is deallocated.
No extra work on that part.
When board is ready to be deallocated, it will send the release message to each object in itself. Even if those objects are NSArrays, they will still be released; and if those arrays are now ready to be deallocated, they will send release to their members, etc.
Your class should implement dealloc normally -- release any ivars that you hold a strong reference to before calling [super dealloc].

out of scope - NSMutableArray error

data = [[NSMutableArray arrayWithCapacity:numISF]init];
count = 0;
while (count <= numISF)
{
[data addObject:[[rouge_col_data alloc]init]];
count++;
}
When I step through the while loop, each object in the data array is 'out of scope'
rouge col data 's implementation looks like this..
#implementation rouge_col_data
#synthesize pos;
#synthesize state;
-(id) init {
self = [super init];
return self;
}
#end
Most tutorials I could find only use NSStrings for objects in these kinds of arrays.
-Thanks
Alex E
EDIT
data = [[[NSMutableArray alloc] initWithCapacity:numISF]retain];
//data = [[NSMutableArray arrayWithCapacity:numISF] retain];
count = 0;
while (count < numISF)
{
[data addObject:[[[rouge_col_data alloc]init]autorelease]];
count++;
}
still the same error, even when switching the 'data = '.
You don't need to call init on the result of your arrayWithCapacity: call. arrayWithCapacity: already returns you an initialized (but autoreleased) object. Alternatively you could call [[NSMutableArray alloc] initWithCapacity:].
Your loop has an off by one error; you're starting at zero, so you'll add an extra object. Adding this extra object will succeed - it just doesn't seem like what you're trying to do.
You probably want to autorelease the objects you're adding to the array. The array will retain them on its own. If you do have some need to retain the objects themselves, that's fine, but it's pretty common to let the array do the retention for you.
You should retain the array itself, otherwise it will vanish at the end of the event loop.
The only error I can spot in your code is your NSArray initialization.
Where you do:
data = [[NSMutableArray arrayWithCapacity:numISF] init];
you should be doing:
data = [NSMutableArray arrayWithCapacity:numISF];
This is because arrayWithCapacity is a factory method, and will return you an autoreleased instance. If you want to keep using the object after this method, you'll need to retain it, and your could will look like:
data = [[NSMutableArray arrayWithCapacity:numISF] retain];

What increases an object's retain count?

Here is code I am referring to.
// Person.h
#interface Person : NSObject {
NSString *firstName;
NSString *lastName;
}
#end
// Person.m
#implementation Person
- (id)init {
if (![super init]) return nil;
firstName = #"John";
lastName = #"Doe";
}
#end
// MyClass.m
#implementation MyClass
.....
- (NSArray *)getPeople {
NSMutableArray *array = [[NSMutableArray alloc] init];
int i;
for (i = 0; i < 10; i++) {
Person *p = [[Person alloc] init];
[array addObject:p];
}
return array;
}
.....
#end
Now, I know there is no memory-management going on in this sample code. What would be required?
In the getPeople loop, I am alloc'ing a Person (retainCount 1), then adding it to array. The retain count is now 2, right? If it is two, should I be [p release]'ing after adding it to the array, bringing the retainCount back down to 1?
Am I right in that it is the caller's responsibility to release the array returned by the method? (Which would also free the memory of the Person's, and their instance variables, assuming their counts are at 1).
I have read Apple's memory management document, but I guess what I am most unclear about, is what increases an objects retain count? I think I grasp the idea of who's responsibility it is to release, though. This is the fundamental rule, according to Apple:
You take ownership of an object if you create it using a method whose name begins with “alloc” or “new” or contains “copy” (for example, alloc, newObject, or mutableCopy), or if you send it a retain message. You are responsible for relinquishing ownership of objects you own using release or autorelease. Any other time you receive an object, you must not release it.
bobDevil's sentence "only worry about the retain counts you add to the item explicitly" made it click for me. After reading the Ownership policy at Apple, essentially, the object/method that created the new object, is the one responsible for releasing /it's/ interest in it. Is this correct?
Now, let's say I a method, that receives an object, and assigns it to a instance variable. I need to retain the received object correct, as I still have an interest in it?
If any of this is incorrect, let me know.
You are correct that the retain count is 2 after adding it to an array. However, you should only worry about the retain counts you add to the item explicitly.
Retaining an object is a contract that says "I'm not done with you, don't go away." A basic rule of thumb (there are exceptions, but they are usually documented) is that you own the object when you alloc an object, or create a copy. This means you're given the object with a retain count of 1(not autoreleased). In those two cases, you should release it when you are done. Additionally, if you ever explicitly retain an object, you must release it.
So, to be specific to your example, when you create the Person, you have one retain count on it. You add it to an array (which does whatever with it, you don't care) and then you're done with the Person, so you release it:
Person *p = [[Person alloc] init]; //retain 1, for you
[array addObject:p]; //array deals with p however it wants
[p release]; //you're done, so release it
Also, as I said above, you only own the object during alloc or copy generally, so to be consistent with that on the other side of things, you should return the array autoreleased, so that the caller of the getPeople method does not own it.
return [array autorelease];
Edit:
Correct, if you create it, you must release it. If you invest interest in it (through retain) you must release it.
Retain counts are increased when you call alloc specifically, so you'll need to release that explicitly.
factory methods usually give you an autoreleased object (such as [NSMutableArray array] -- you would have to specifically retain this to keep it around for any length of time.).
As far as NSArray and NSMutableArray addObject:, someone else will have to comment. I believe that you treat a classes as black boxes in terms of how they handle their own memory management as a design pattern, so you would never explicitly release something that you have passed into NSArray. When it gets destroyed, its supposed to handle decrementing the retain count itself.
You can also get a somewhat implicit retain if you declare your ivars as properties like #property (retain) suchAndSuchIvar, and use #synthesize in your implementation. Synthesize basically creates setters and getters for you, and if you call out (retain) specifically, the setter is going to retain the object passed in to it. Its not always immediately obvious, because the setters can be structured like this:
Person fart = [[Person alloc] init];
fart.firstName = #"Josh"; // this is actually a setter, not accessing the ivar
// equivalent to [fart setFirstName: #"Josh"], such that
// retainCount++
Edit:
And as far as the memory management, as soon as you add the object to the array, you're done with it... so:
for (i = 0; i < 10; i++) {
Person *p = [[Person alloc] init];
[array addObject:p];
[p release];
}
Josh
You should generally /not/ be worried about the retain count. That's internally implemented. You should only care about whether you want to "own" an object by retaining it. In the code above, the array should own the object, not you (outside of the loop you don't even have reference to it except through the array). Because you own [[Person alloc] init], you then have to release it.
Thus
Person *p = [[Person alloc] init];
[array addObject:p];
[p release];
Also, the caller of "getPeople" should not own the array. This is the convention. You should autorelease it first.
NSMutableArray *array = [[[NSMutableArray alloc] init] autorelease];
You'll want to read Apple's documentation on memory management: http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html

Releasing With NSMutableArray?

I am allocating myMDD in main which contains an NSMutableArray instance variable (alloc/init-ed in init). When I add items to the NSMutableArray (frameList) I release after adding. The array and the objects it now contains are released at the bottom of main.
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
MDD *myMDD = [[MDD alloc] init];
Frame *myFrame = [[Frame alloc] init];
[myMDD addFrame:myFrame];
[myMDD release];
[pool drain];
return 0;
}
// METHOD_ mdd addFrame:
-(void)addFrame:(Frame*) inFrame {
[frameList addObject:inFrame];
[inFrame release];
}
// METHOD_ mdd dealloc
-(void)dealloc {
NSLog(#"_deal...: %#", self);
[frameList release];
[super dealloc];
}
My question is that the "static analyser" reports a potential memory leak, prefering to have the release for frame added main. (i.e)
int main (int argc, const char * argv[]) {
...
[myFrame release]; // Added
[myMDD release];
[pool drain];
return 0;
}
// METHOD_ mdd addFrame:
-(void)addFrame:(Frame*) inFrame {
[frameList addObject:inFrame];
// [inFrame release];
}
I can see why this is, if I alloc myMDD and never call addFrame then I need to release it. Maybe its just a case of adding a autorelease to myMDD, but would that work in the situation where I call addFrame and the NSMutableArray is releasing the object?
EDIT_001
Changed to ...
int main (int argc, const char * argv[]) {
...
[myMDD addFrame:myFrame];
[myFrame release];
myFrame = nil;
[myMDD release];
[pool drain];
return 0;
}
// METHOD_ mdd addFrame:
-(void)addFrame:(Frame*) inFrame {
[frameList addObject:inFrame];
}
gary
The reason you got that warning is because an NSMutableDArray retains any object put into it; likewise, when an NSMutableArray is released, it also releases any object contained within it. So let's look at your code.
This line:
Frame *myFrame = [[Frame alloc] init];
creates a new instance of Frame called myFrame. myFrame has a retain count of 1, because you used alloc/init to create it.
You then pass this to addFrame::
[myMDD addFrame:myFrame];
Which in turn puts it into an instance of an NSMutableArray:
[frameList addObject:inFrame];
At this point, inFrame and myFrame point to the same object. When added to the array, this object's retain count is incremented, so now it is 2.
Later on, back in main, you release myMDD, which releases frameList. Assuming frameList now has a retain count of 0, it is deallocated -- and, as an NSMutableArray, it releases any object it contains, which includes the object pointed to my myFrame.
So now myFrame's retain count is 1...so it doesn't get released, and you have a memory leak.
One Cocoa-y way to solve the problem is by autorelease myFrame:
Frame *myFrame = [[[Frame alloc] init] autorelease];
Which means it won't leak. Then, use the -[MDD dealloc] method in your second example (Edit_001). You're right that you shouldn't release inFrame in your addFrame method, since you're not retaining it.
As per convention, an add method should just retain the object if needed, not release it. And as a general rule, you should not release object that you did not retain, in your example the scope where you retained (created) the frame is not the same as in the addFrame method.
By scope I mean logic scope, not language scope.
In that particular example, you must call release just after addFrame. But the release should not be in the addFrame method.
In most cases, Cocoa provides class methods that initialize and return an autoreleased version of an object. i.e. [NSMutableDictionary dictionary] vs [[NSMutableDictionary alloc] init].
I advise to always use the class methods where possible if you're creating an object that you won't need to keep around or if you going to store it in a collection (NSArray, NSDictionary, NSSet, etc).
The general rule then is to only alloc objects your class owns directly (i.e. an instace or class variable, not inside a collection) and to use the class methods for all other cases.