views:

703

answers:

2

Hi to all!

I dropped 2 days on this problem and can't come out with a solution so: (I know this wont have much sense but hopefully it will do the trick)

monkey.h

@interface monkey : NSObject {
NSNumber *monkeyRanch;
}
@property (nonatomic, retain) NSNumber *monkeyRanch;
-(id) gatherTheMonkeys:(int)howmany;

monkey.m

@synthesize monkeyRanch;
-(id) gatherTheMonkeys:(int)howmany{
NSNumber *temp=[[NSNumber alloc] initWithInt:howmany];
monkeyRanch = temp;
[temp release];
return [monkeyRanch autorelease];
}

appDelegate.m

theMonkeys = [[monkey alloc] gatherTheMonkeys:3];
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];

..and theMonkeys are released in dealloc. This works as expected. No leaks in Instrument. BUT. If I try the same with an NSMutable array instead of the NSNumber:

monkey.h

@interface monkey : NSObject {
    NSMutableArray *monkeyRanch;
}
@property (nonatomic, retain) NSMutableArray *monkeyRanch;
-(id) initTheMonkeys:(int)howmany;

monkey.m @synthesize monkeyRanch;

-(id) initTheMonkeys:(int)howmany{
monkeyRanch=[[NSMutableArray alloc] init];
NSNumber *imp =[[NSNumber alloc] initWithInteger:howmany];
[monkeyRanch addObject:imp];
[imp release];
return [monkeyRanch autorelease];
}

appDelegate

theMonkeys = [[monkey alloc] initTheMonkeys:3];
[theMonkeys retain];

this causes a leak (of an object 'monkey') just at the start of the application.

Also tried to change the initTheMonkeys to the following:

NSMutableArray *temp=[[NSMutableArray alloc] init];
monkeyRanch=temp;
[temp release];
NSNumber *imp =[[NSNumber alloc] initWithInteger:howmany];
[monkeyRanch addObject:imp];
[imp release];
return [monkeyRanch autorelease];

but the retain count of monkeyRanch got to zero as releasing temp and the app crashed happily.

I know there is something simple and obvious I'm missing.. (and yes I read the mem management docs but still missing something)

Any help much appreciated.

+1  A: 

Indeed, you're missing this:

theMonkeys = [[monkey alloc] initTheMonkeys:3];
[theMonkeys retain];

When you 'init' something, you end up with an implicit 'retain' (the retain count is one). There's your leak; you're both init'ing it and retain'ing it.

AlBlue
If the retain is removed, the returned autorelease objects drags theMonkeys down to 0 retaincount and the program... :)
Daniel K
+2  A: 

Your memory problems boil down to an issue of ownership. When a class has an instance variable (such as monkeyRanch), you typically create an instance in the initializer for that class, and then release it in dealloc, like so:

@interface Monkey : NSObject {
    NSMutableArray * monkeyRanch;
}
@end

@implementation Monkey
- (id)init {
    if ((self = [super init]) == nil) { return nil; }
    monkeyRanch = [[NSMutableArray alloc] initWithCapacity:0];
    return self;
}
- (void)dealloc {
    [monkeyRanch release];
}
@end

In this case, the Monkey class owns the monkeyRanch member.

Within function calls, you must be sure to release any local objects that you init, retain or copy. It looks like you have this part correct, but I'll show a quick example all the same:

- (void)someFunction {
    NSNumber * myNumber = [[NSNumber alloc] init];
    ...
    [myNumber release];
}

The next thing to understand is that there is a huge difference between simply setting a member variable, and setting a property value. That is to say:

NSMutableArray * temp = [[NSMutableArray alloc] initWithCapacity:0];
monkeyRanch = temp;
[temp release]; // it's gone!

is not the same thing as:

NSMutableArray * temp = [[NSMutableArray alloc] initWithCapacity:0];
self.monkeyRanch = temp; // now owned by self
[temp release];

In the first case, you are simply setting a pointer (monkeyRanch) equal to another pointer (temp). The value is not retained, or copied.

In the second case, you are actually setting the property. This means that, assuming the property was set to retain, the array is now owned by your class object.

P.S.:

You may also be going down the wrong track design-wise. To my mind, a monkey ranch is something that has a lot of monkeys in it. If that's true, then your Monkey objects really shouldn't own the MonkeyRanch. I would instead go with something like this:

Monkey.h

@class MonkeyRanch;

@interface Monkey : NSObject {
    MonkeyRanch * ranch;
}
@property (nonatomic, assign) MonkeyRanch * ranch;
@end

Monkey.m

@implementation Monkey
@synthesize ranch;
@end

MonkeyRanch.h

#import "Monkey.h"

@interface MonkeyRanch : NSObject {
    NSMutableArray * monkeys;
}
@property (nonatomic, retain) NSMutableArray * monkeys;
- (id)initWithNumberOfMonkeys:(int)howMany;
- (void)dealloc;
@end

MonkeyRanch.m

@implementation MonkeyRanch

@synthesize monkeys;

- (id)initWithNumberOfMonkeys:(int)howMany {
    if ((self = [super init]) == nil) { return nil; }

    monkeys = [[NSMutableArray alloc] initWithCapacity:howMany];
    for (int index = 0; index < howMany; index++) {
        Monkey * newMonkey = [[Monkey alloc] init];
        [newMonkey setRanch:self];
        [monkeys addObject:newMonkey];
        [newMonkey release];
    }

    return self;
}

- (void)dealloc {
    [monkeys release];
}

@end

Usage:

#import "MonkeyRanch.h"

MonkeyRanch * theRanch = [[MonkeyRanch alloc] initWithNumberOfMonkeys:3];

...

[theRanch release];
e.James
Thanks James! I was missing the property value and member variable setting difference. And I always wanted to return the property instead of the class object itself. If I guess right I can now access my classes property(the array of monkeys) like theRanch.monkeys? (is there any solution for what I tried to do?)
Daniel K
Yes, you can access the array via `theRanch.monkeys`. You've got it now :) but to be honest, I'm not quite sure what it was that you were trying to do. Perhaps you should post another SO question along the lines of "How would you structure a class for this kind of data?"
e.James