views:

81

answers:

3

I've read a few posts on this, but there's still one thing that's not clear for me. I know this might be rather a n00b question, but I've actually got rather far into development without quite grasping this fundamental issue. A symptom of being self taught I guess.

You declare a variable in your header, like so:

@interface SomeClass : NSObject {
    NSMutableArray *anArray;
}

@property (nonatomic, retain) NSMutableArray *anArray;

end

And then in your main file you synthesise it and set it to an initial value:

    @implementation SomeClass

@synthesize anArray

- (SomeClass *)init{
    if (self = [super init]) {
        self.anArray = [[NSMutableArray alloc] initWithCapacity:10];
}
[return self];

And release it when your Class deallocs:

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

Now, when I run instruments, the line

self.anArray = [[NSMutableArray alloc] initWithCapacity:10];

is identified as a memory leak. Is it a memory leak because when you define the variable anArray in the header it allocates memory? (Because I thought it was a null pointer.) Therefore when you want to initialise it, and you call [[NSMutableArray alloc] initWithCapacity:10], you are reallocating the memory, and losing the pointer to the original allocation?

So instead, I use the convenience class method:

@implementation SomeClass

    @synthesize anArray

    - (SomeClass *)init{
        if (self = [super init]) {
            self.anArray = [NSMutableArray arrayWithCapacity:10];
    }
    [return self];

This is no longer identified as a memory leak in instruments. And since it's a convenience method, anArray is autoreleased. However, if I am to assume that the instance declaration in the header allocates memory, which would explain the previous issue, then should I still release anArray? Does setting the initial values in this way retain it perhaps?

I understand the difference between

NSMutableArray *anArray = [[NSMutableArray alloc] initWithCapacity:10];

and

NSMutableArray *anArray = [NSMutableArray arrayWithCapactiy:10];

but what I'm not sure I understand is when you've declared NSMutableArray *anArray in your header, which of the two approaches you should use and why. And whether or not if you use the second approach, you should still release anArray when you call dealloc.

I might add that I've found the following posts/links useful:

+2  A: 

It's not the instance that allocates the memory. You're right to assume that in Objective-C (at least on all Apple-based operating systems), newly initialized classes have all their ivars set to 0 (or nil or NULL as appropriate).

The problem you're seeing is that you're using the property, not the ivar in your initialization. Since you declared your property as retain, using the property accessor to set it automatically retains it.

So, when you initialize you either have to take ownership and set the ivar directly, or do like you're doing and use the property accessor to set the property and then relinquish ownership in the init method (by either releasing an object you own or, as you did in your second instance, using the convenience constructor so that you never owned the returned instance).

So just remember, if you ever use the property accessors, even within the class itself, you will get the features you set on the property (e.g., nonatomic, retain, etc.). You use the property accessors whenever you do one of the following:

// in these cases the property takes ownership through the
// retain keyword, so you must not take ownership yourself
self.anArray = something;
[self setAnArray:something];
[self setValue:something forKey:@"anArray"];

You would access your ivar directly like:

anArray = something; // in this case you must take ownership
Jason Coco
Thanks for the detailed answer - it really helps. The one bit I don't get it when you say "use the property accessor to set the property and then relinquish ownership in the init method (by either releasing an object you own..." So if I call self.anArray = [[NSMutableArray alloc] init] in the init method, I need to release it in the init method too? And also when I dealloc? So that's why people sometimes do NSArray *anArray = [[NSArray alloc] init]; self.classArray = anArray; [anArray release]; ?
Smikey
@Smikey: Yes, that's exactly why they do that. So if you do the original (i.e., self.anArray = [[NSMutableArray alloc] init]) you are basically guaranteed to leak it. Basically, since the property is declared as "retain", it takes ownership, so you need to relinquish ownership (i.e., release it).
Jason Coco
So my options are: anArray = [[NSArray alloc] init] (must call [anArray release] in dealloc only || self.anArray = [NSArray array] (no need to release anything) || if I DID do self.anArray = [[NSArray alloc] init] (then must call [anArray release] in init method ASWELL as in dealloc method (BAD)) || finally, the method I mentioned in my comment. And which is better out of anArray = [[NSArray alloc] init] and self.anArray = tempArray (where tempArray is alloced and released as above). Thanks so much btw!
Smikey
Any time you have a copy or retain attribute, you must release it in dealloc, so that doesn't change. You're still responsible for cleaning that up. The only difference is how you /assign/ it in the first place. Inside your class, you can assign it directly to the ivar, in which case you must keep and maintain ownership (i.e., create and not release it or retain it). If you assign it through property accessors, you must release ownership (e.g., if you create it, you must release it).
Jason Coco
Ooooh k... I think I see. So it's better not to use self.ivar to assign values in the init method for a class, since you'll then need to release the ivar. However, if you assign values directly to the ivar, then the releasing and retaining depends on how you assign it - either with a class method (autoreleases) or with an instance method (you must still release it). Is that right? Thanks for your patience btw :s
Smikey
When you say you must keep and maintain ownership, doesn't that mean you're responsible for releasing and retaining it? Whereas if you don't keep or maintain it, you don't have to release/retain it. I think that's where I'm getting confused with your last comment :o
Smikey
+3  A: 

alloc'ing an object starts it off with a reference count of 1. Setting a property that has the 'retain' attribute also increases the reference count.

So, that means this is usually bad:

@property (nonatomic, retain) Object * variable;

...

self.variable = [[Object alloc] init];

Because variable now has a reference count of 2.

When setting a object's member variable, just do this:

variable = [[Object alloc] init];

You should also realize that this works

        self.anArray = [NSMutableArray arrayWithCapacity:10];

Because "arrayWithCapacity" (and other similar factor methods) autoreleases the object it returns, so after you set the property, it essentially has a reference count of 1.

whooops
Thanks for making it so clear. So if I do self.anArray = [NSMutableArray arrayWithCapacity:10]; I STILL need to release anArray when I dealloc, since it still has a reference count of 1. Does the reference count go up by 1 every time I call self.anArray = something then? In which case it would be safer to set the ivar directly, without using self?
Smikey
Yes, you would still need to release it. The number doesn't "go up" every time you call 'self.anArray = something', because the reference count is tied to the object, not the variable. Normally, 'self.anArray = something' will release the old object and retain the new one.
whooops
It's generally good practice to use 'self.varname = something', because that way you will always release the old object. The exception, as I showed, is in the init method, and you've just alloc'd the object, and there's definitely no previous object stored in the variable.
whooops
Great, thanks for the clarification!
Smikey
A: 
@interface SomeClass : NSObject
{
    NSMutableArray *anArray;
}

@property (nonatomic, retain) NSMutableArray *anArray;

@end

@implementation SomeClass

@synthesize anArray

- (SomeClass *)init {

/* if (self = [super init]) << bad -- `self` may be evaluated before assignment */

/* safe version follows */
    self = [super init];
    if (0 != self) {
        enum { BAD = 0 };
        if (BAD) {
        /* adds second retain by using the accessor, also bad because you're using instance methods on a partially initialized instances. this `convenience' can blow up on you in so many ways */
            NSMutableArray * tmp = [[NSMutableArray alloc] initWithCapacity:10];
            /* tmp no has a retain count of 1 */
            self.anArray = tmp;
            /* tmp/anArray no has a retain count of 2 */
        }
        else {
        /* SomeClass declared anArray, SomeClass is responsible for making sure it is initialized properly -- just set the pointer to an allocated instance with a ref count of 1, as shown: */
            anArray = [[NSMutableArray alloc] initWithCapacity:10];
        }
    }
    [return self];
}

- (void)dealloc {
    [anArray release];
    [super dealloc];
}
Justin
also, it should be declared `-(id)init;`, rather than `- (SomeClass *)init;`
Justin
So you shouldn't using the self keyword when setting ivars to initial values UNLESS you're using a convenience method. So self.anArray = [[NSArray alloc] initWithCapacity:10] = BAD. But self.anArray = [NSArray arrayWithCapacity:x] = OK. And anArray = [[NSArray alloc] initWithCapacity:10] = GOOD. And in your if(BAD) clause, would it then be ok if you called [tmp release]; ?
Smikey
Self will /not/ be evaluated before assignment. In C, assignment precedence is right-to-left. If you have other logical tests in the same if statement, just surround the assignment in parenthesis which is what's typically done when testing the result of assignment anyway.
Jason Coco
@Smikey no - you should avoid calling instance methods (upon self) altogether during initialization and dealloc. there are a few exceptions (like when you know your class is never subclassed), but it is best to avoid for many reasons. it is sure to cause bugs which are difficult to track (although you may get away with it more often than not).
Justin
@Jason Coco the expression `if (self = [super init]) { ... }` is used in the original post - i have seen that fail. no need to dispute it. ``if ((self = [super init])) { ... }` is evaluated differently - that is fine (as well the way i wrote it). either my way or the way with the extra parentheses will evaluate properly. i prefer the 2 line declaration for a few reasons.
Justin
Jason Coco
@Justin So if I shouldn't call self.ivar in the init method, and instead call ivar = [object alloc] init], I should add a retain if I want to access the same ivar elsewhere in the class? So the ref count of self.ivar = [object alloc] init] is the same as ivar = [[[object alloc] init] retain]; ?
Smikey
@Smikey you'll have to retain it if you are returned an autoreleased object and you assign the pointer directly (as opposed to using a retained property). there are a few idioms to denote this in Cocoa frameworks, namely the pattern `+[NSDerived derivedWithString:]` returns an autoreleased object, where `-[NSDerived initWithString:]` will return an object you are responsible for sending a release message when you are finished using it. in this case (pointer assignment during initialization), you'll have to increment the reference count (by sending it a retain message) of an autoreleased object
Justin
"self.ivar = [object alloc] init] is the same as ivar = [[[object alloc] init] retain];" in the sense that you will hold 2 references to an object. however, self should hold only one reference (because it refers to it only by one instance variable 'anArray'. `ivar = [[[object alloc] init];` and `ivar = [[object objectWithTile:a] retain];` are the forms you'll use because you want just one reference count.
Justin
Perfect - thanks Justin. I'm pretty sure I get it now. Thanks so much for the explanation. I imagine it seems simple once you get your head around it :) I appreciate the help.
Smikey
it is really quite simple, but you need to understand it to write cocoa apps. for more details, see: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html
Justin
Haha I've read it. Maybe I need to give it another read through. One other thing - should one also avoid using self when initialising viewControllers in the viewDidLoad method? I'd assume so.
Smikey
it may be a good idea - it's pretty short, and well written. one thing that makes it extra difficult to learn is to overuse autorelease pools (e.g. messaging autorelease when you could message release 2 lines later). avoiding autorelease pools is a performance benefit, as well as localizes many over-releases and leaks, making them far easier to isolate while affirming your understanding of reference counting. it is fine to use `self` in `- [UIViewController viewDidLoad]`; the view controller and its outlets have been established/initialized.
Justin