Daniel K
Daniel K

Reputation: 53

How am I mismanaging my iPhone's memory?

This code works:

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. There are 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.

I 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.

What am I doing wrong, and how can I fix it?

Upvotes: 1

Views: 814

Answers (2)

James Eichele
James Eichele

Reputation: 119106

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];

Upvotes: 3

AlBlue
AlBlue

Reputation: 24040

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.

Upvotes: 1

Related Questions