After getting the objectEnumerator in the following code, the set1 retain count goes to 3. I was surprised to see that because I didn't expect it to change. I searched the documentation and can't find where this effect is explained.
I assume the extra retains are probably set to autorelease by the Cocoa enumeration logic and won't really have any effect in the current event loop. It makes sense the objectEnumerator logic would need a reference to set1 but I'd like to know why they were made. Here is the reason: if I assume set1 has retain count zero after the release in the code then I could try to reuse it another new set. Wouldn't that cause problems since set1 is now pointing at a completely different object/address?
For "bonus" points, is there a way of enumerating the autorelease pool see what it actually contains? TIA
#import <Foundation/NSObject.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSString.h>
#import <stdio.h>;
// macro to create an integer number:
#define INTOBJ(v) [NSNumber numberWithInt: v]
int main (int argc, char *argv[])
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
//Make set
NSMutableSet *set1 = [[NSMutableSet alloc] initWithObjects:
INTOBJ(1), INTOBJ(2), INTOBJ(5), INTOBJ(10), nil];
printf("set1 #%lu\n", [set1 retainCount]);
//Get enumerator of the set. This is where the retain count goes to 3:
NSEnumerator *setEnum = [set1 objectEnumerator];
printf("setEnum #%lu\n", [setEnum retainCount]);
printf("set1 #%lu\n", [set1 retainCount]);
//Iterate through the collection:
printf("[");
NSNumber *element;
while ((element = [setEnum nextObject]) != nil)
//do some this with item. printf is just for debugging:
printf(" %i ", [element intValue]);
printf("]\n");
printf("set1 #%lu\n", [set1 retainCount]);
[set1 release];
printf("set1 after release #%lu\n", [set1 retainCount]);
//More logic could go here reusing variable set1 since I assumed retain count = 0
[pool release];
return 0;
}
It's generally not a good idea to rely on the retain count of objects, as it's an internal detail of the framework. Instead make sure your code adheres to the memory management principles, particularly ensuring that retain/new/copy and release/autorelease are balanced.
Presumably, the enumerator is retaining the collection so that it doesn't get deallocated during enumeration. An enumerator without a valid collection to enumerate wouldn't work very well. In fact, the only way for the enumerator to be sure that it will work is to retain the collection it enumerates.
That said, there's really no reason to ever look at the retain count of any object except for debugging a memory leak/double-release problem. As long as you follow the memory management conventions, you should never need to worry about an object's retain count.
Reusing set1 after the release won't cause problems, because the retain count is on the Object referenced by the variable set1, not on the variable itself.
Related
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.
I can never seem to deallocate my NSMutableString as shown below. The initial retain count should be 1, but after releasing several times, the string is still usable like nothing happened!
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// insert code here...
NSMutableString* s = [[NSString alloc]initWithString:#"AAA"];
[s release];
[s release];
[s release];
[s release];
[s release];
NSLog(#"%#",s);
[pool drain];
return 0;
}
Of course, if I use Analyze it still tells me that I release a released object on the second release.
Scott's answer is the correct general one, but in this particular case, the reason is that NSString literals (i.e. #"") are uniqued compile-time constants and do not actually do anything at all when retained and released. Your assignment of it to an NSMutableString* does not actually make it an NSMutableString, so what you've written is equivalent to
[#"AAA" release];
[#"AAA" release];
[#"AAA" release];
[#"AAA" release];
[#"AAA" release];
[#"AAA" release];
Releasing an object tells the runtime that it can destroy the object, at least as far as you're concerned, but it doesn't require that the object be destroyed immediately: After your first [s release], Cocoa is free to do whatever it pleases with the memory formerly used by s. It might give that memory to the next object that does an alloc, in which case your later attempts to access s will result in a fiery runtime crash… or it might not need that memory right away, in which case you might get away with accessing a released object.
The rule of thumb is less "I've released this object, which means it no longer exists" and more "I've released this object, which means it's no longer guaranteed to exist."
Here is what i have in my main method:
int main (int argc, const char * argv[]) {
NSString *string=[[NSString alloc]init];
NSLog(#"%# The retain count is:%d", string, [string retainCount]);
NSLog(#"Hello, World!");
return 0;
}
The output on executing this piece of code is "The retain count is :-1".
I was expecting it to give me 1 as the retain count, since i created the object using alloc.
I have similar problems when i try to retain or release the NSString objects.
When i try with NSArray, i get the retain count immediately after creation of object as 2. I do not understand the mechanism of how the objects are being treated.
Please explain!
Thanks in advance..
You shouldn't focus on the raw retain count value, as this can be a different value depending on various things. For example, in your first line you are allocating what is the equivalent of #"", that is an empty string. This is optimized by an object that cannot be deallocated and is shared wherever you're "allocating" it. The same is true for all static strings, e.g. look the value returned by [#"Hello" retainCount].
Instead you should focus on relative retain count. That is, is the retain count increased (+1), does it stay the same (+0) or is it decreased (-1). You always need to make sure that these relative value sum up to 0.
An example:
Foo *foo = [[Foo alloc] init];
[foo retain];
[foo release];
[foo autorelease];
The alloc method returns an object with +1, and retain also increses the count by 1. So by the end of line 2, we're at +2. Both release and autorelease have the effect of -1, so at the end of line 4 you're at +0 and everything is fine.
There are a few simple rules: if the method name is alloc, starts with the words copy or create then you will get an object with retain count +1. All other methods (especially those start with get) should return objects with retain count +0. If your method needs to create an object but its name implies that a +0 object should be returned, you can do this:
- (Foo *) bar {
Foo *result = [[Foo alloc] init]; // +1
return [result autorelease]; // -1
// Object lives until the autorelease pool is drained,
// but relative retain count is +0.
}
-1 is what you get if you cast UINT_MAX to a signed value. UINT_MAX is the documented retain count of objects which are never released, which includes string literal objects.
Speaking of the documentation, I suggest reading the big box about how retainCount is generally not useful.
I'm trying to get my head around memory management in Objective - C. I've used the garbage collector up until this point but before I go forward I'd like to get a better understanding of manually managing memory. I'm aware that I don't have an implementation of a dealloc method in this code.
My question is why does my inputString variable have a retain count of eleven here?
#import "AppController.h"
#implementation AppController
-(id) init
{
[super init];
NSLog(#"init");
speechSynth = [[NSSpeechSynthesizer alloc] initWithVoice:nil];
NSLog(#"speechSynth retain count is %d",[speechSynth retainCount]);
return self;
}
-(IBAction) count:(id) sender
{
NSString *outputString;
int numberOfCharacters;
inputString = [textField stringValue];
numberOfCharacters = [inputString length];
outputString = [NSString stringWithFormat:#"\"%#\" has %d characters",inputString,numberOfCharacters];
[label setStringValue:outputString];
[speechSynth startSpeakingString:outputString];
NSLog(#"outputString retain count is : %i",[outputString retainCount]);
NSLog(#"inputString retain count is: %d",[inputString retainCount]);
NSLog(#"speechSynth retain count is: %d",[speechSynth retainCount]);
[outputString release];
}
#end
Apple's answer is "it doesn't matter." Track your references properly and let the runtime sort out the rest.
Internally, the runtime may be giving you a pointer to a singleton empty string (since NSStrings are immutable). Or it may be doing something else. But the reasoning behind a reference count for a variable that from your perspective has just been allocated is considered to be runtime internals, and you shouldn't rely on it for anything.
Use Instruments and zombie objects to figure out if you're leaking or over-releasing, and pretend that the retainCount message doesn't exist.
What retain count do you think inputString should have? Bear in mind you got it from the Cocoa framework and who knows how many different objects inside it have references to it - 11 probably.
Look at the Memory Management Rules. They don't mention retain counts at all and that's for a very good reason: they are pretty much useless as a debugging tool.
From the Apple documentation:
Important: Typically there should be
no reason to explicitly ask an object
what its retain count is (see
retainCount). The result is often
misleading, as you may be unaware of
what framework objects have retained
an object in which you are interested.
In debugging memory management issues,
you should be concerned only with
ensuring that your code adheres to the
ownership rules.
Apple Documentation
Thanks for the help everyone.
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