1

why do I need this autorelease after [NSMutableArray array] to avoid a memory leak?

That is Instruments told me there was a leak. By putting the autorelease in it solved it, however I'm not sure why this would be required. The "array" method wasn't like an INIT or COPY etc...

@interface Weekend : NSObject {
    NSMutableArray*     _events;     
}
@property (nonatomic, retain) NSMutableArray* events;
@end

@implementation Weekend

@synthesize events = _events;

- (id)init {
    if (self == [super init])
    {
        self.events = [[NSMutableArray array] autorelease];    // WHY IS THIS AUTORELEASE REQUIRED
    }
    return self;
}

- (void) dealloc {
    [_events release];  _events = nil;
    [super dealloc];
}

@end

NOTE: This is what I see in Intruments when I take autorelease out (and after I changed the "if (self == [super init])" to "if ((self = [super init]))"

#  Category     Event        Code Location
0  __NSArrayM   Malloc       at the [NSMutableArray array] point
1  __NSArrayM   Autorelease  at the [NSMutableArray array] point
2  __NSArrayM   Retain       at the @synthesize events = _events; point of the code
3  __NSArrayM   Release      QuartzCore - CA:Transaction::observer_callback(__CF........)
                             (from main.m:14 - "int retVal = UIApplicationMain(argc, argv, nil, nil);")
Greg
  • 34,042
  • 79
  • 253
  • 454
  • 4
    You don’t need that `-autorelease`. In fact, you shouldn’t since `+array` returns an object that you do not own and `events` is a `retain` property. Also, you’re not assigning `[super init]` to `self` — you should change `==` to `=`. Most likely you’re leaking a `Weekend` object, which in turns leaks its `_events` array. –  Jun 02 '11 at 23:53
  • Dunb question, but if I'm pausing/stopping instruments during the running ofthe app, then could I actually just be seeing the fact that the code to release the parent hasn't been hit? – Greg Jun 03 '11 at 00:25

3 Answers3

5

Why do you need that extra release? You don't. Not there, anyway.

The problem is you're overretaining _events somewhere else. Maybe you're passing it to another class that's retaining without releasing? Leaks are always attributed by Instruments to creation of the object, not the unbalanced retain.

Adding that autorelease instead of finding the unbalanced retain is the memory management equivalent of having an answer that's off by 0.3 and just adding 0.3 to fix it. You need to remove that and fix the real problem.

Edit: After reading your latest edit, I think you'll probably find that Weekend itself is being leaked.

Steven Fisher
  • 44,462
  • 20
  • 138
  • 192
  • oh - I've added into the post now what I see in instruments - so the fact that the first Retain is from the @synthesize point doesn't something, or I guess maybe this is valid, and what's not occuring correctly then is the user Weekend object isn't releasing this object? But what about the final release then? Is this normal? – Greg Jun 03 '11 at 00:12
  • updated the post Steven - not sure what the final Release is? – Greg Jun 03 '11 at 00:13
  • I'm not sure either. :) The first malloc/autorelease are from the `[NSMutableArray array]`. The retain is from @synthesize. That's all good to this point. You should be seeing a release from your dealloc. That you're not seems to mean the dealloc isn't happening. Maybe confirm this by adding `NSLog(@"I'm going away now!");` to `dealloc`. If you run and don't see that message, you need to chase a leak of `Weekend` instead of that array. I'd probably just ignore the last release for now: if you're seeing a leak, that release can't be real. – Steven Fisher Jun 03 '11 at 00:15
  • Bleh, I've edited my comment several times. If you saw an earlier version, make sure you reread. (Sorry.) – Steven Fisher Jun 03 '11 at 00:19
  • Dunb question, but if I'm pausing/stopping instruments during the running ofthe app, then could I actually just be seeing the fact that the code to release the parent hasn't been hit? – Greg Jun 03 '11 at 00:24
  • Just run it with the basic debugger to see if the NSLog gets executed. – Steven Fisher Jun 03 '11 at 00:47
  • thanks Steven - I've been doing this - it looking like my parent object release I had put in the controllers viewDidUnload, but when the navigationController pops back to the previous view I see that the controller "dealloc" method is called, but "viewDidUnload" doesn't seem to be called - I'll try moving this – Greg Jun 03 '11 at 02:11
  • Steven - fantastic - solved it - so it was the fact I'd assumed that releasing the parent in the viewDidUnLoad was OK - in fact in a normal pop it seems dealloc is called but not viewDidUnLoad - I released the parent in the dealloc as well (they both included setting the parent to nil, so I understand if both methods did get called it should be ok) – Greg Jun 03 '11 at 02:24
  • Yeah. `viewDidUnload` does not always get called on `dealloc`. See here: http://stackoverflow.com/questions/1285932. Personally, my approach has been to create a `-(void)unloadOutlets` which I call from `viewDidUnload` and `dealloc` both. – Steven Fisher Jun 03 '11 at 07:06
2

This line is wrong:

self.events = [[NSMutableArray array] autorelease];

You shouldn't be autoreleasing there. If didn't call alloc/init and used a class method that returns an object, by convention, those objects are return autoreleased.

You might think that autoreleasing it again will prevent a leak what you are doing is causing the object to dealloc too soon because of uneven releases. Chances are is that, while this seems like it's releasing the object, something else is probably retaining and will probably crash later.

Here is what is going on:

Autorelease is a pool that basically tells the system to queue up a release for later. Read up on NSAutoreleasePool. There is an NSAutoreleasePool at the top of the mainloop in the mainthread usually.

Something is else is that the array function will return an auto released version by convention.

The + (NSArray *)array function looks something like this:

+ (NSArray *) array {
   NSArray *ret = [[NSArray alloc] init];
   return [ret autorelease];
}

Your property also has a "retain" flag so the synthesized methods look something like this:

- (NSArray *)events
{
    return [[_events retain] autorelease]; 
}
- (void)setEvents:(NSArray *)anEvents
{
    if (_events != anEvents) {
        [_events autorelease];
        _events = [anEvents retain];
    }
}

The setter here will retain the array that is passed in.

The problem is that you are autoreleasing an object was already just autoreleased, and then only retaining 1 more time. You have an embalance.

Basically retain count started off at 1 (as all objects do when they are alloc'd), was scheduled to autorelease down twice (which will subtract two retains when the pool is flushed at some point in the future) and it's retained once by the setter. By the end, retain count will zero and the object will be freed when you don't expect it.

basically:

  1. [NSMutableArray array] ... array's retain count is 1, autoreleases queued to subtract 1
  2. [array autorelease] ... array's retain count is still 1, but now 2 autoreleases are queued so subtract 2
  3. [self setEvents:...] ... array is retained 1 more time so now count is 2, auto release queue still has it queued to subtract 2...
  4. (sometime later when [NSAutoreleasePool drain/release] is called, usually by event loop pump).. array's is released twice because it was queued for autorelease previously twice... retain count equals 0. array is free'd.

Now something maybe retaining your array somewhere else which is why it's sticking around.

Zac Bowling
  • 6,508
  • 1
  • 27
  • 26
  • he's leaking when no autorelease is passed to [NSMutableArray array]. Nevertheless, releasing object on autorelease with retain count 1 will in lots of cases result in memory leak reported by Instruments. My coworker experienced this couple of days ago. Running static analyzer pointed to the error right away. Will you please comment on my answer? I'd like to hear opinions. – TheBlack Jun 03 '11 at 01:48
  • thanks Zac - did you see my question - Dunb question, but if I'm pausing/stopping instruments during the running ofthe app, then could I actually just be seeing the fact that the code to release the parent hasn't been hit? – Greg Jun 03 '11 at 01:50
  • I've just put my separate "dumb question" here: http://stackoverflow.com/questions/6222021/does-an-entry-in-instruments-leaked-block-during-application-running-imply-memo – Greg Jun 03 '11 at 01:51
  • Check this out: https://gist.github.com/831285 It's crazy but it's fun to watch the log output. If you set a breakpoint on retain, you can see who is holding on to your object. – Zac Bowling Jun 03 '11 at 02:12
0

For start change if (self == [super init]) into if (self = [super init])

Then run static analyzer first, fix issues and then go Leak hunting with Instruments. Either way, I'm pretty sure leak will disappear when you fix if (self == ...) error.

Note: If you cannot reproduce leak in this way, try couple of times more. What happened for the first couple of times I ran this code is this (taken from The Obj-C Programming language):

You should assign self to the value returned by the initializer because the initializer could return an object different from the one returned by the original receiver.

In my case, self became nil after it got into if block, obviously... Does someone knows under which circumstances this might happen?

Clarification:

if (self == [super init]) // End result is YES, execution goes inside of the block but **==** DOES NOT ASSIGN [super init] object to self, single **=** DOES. See the difference? == vs =?
{
    self.myArray = [NSMutableArray array]; // Allocates array on autorelease, assigns it to nil object trough property (code below) which increases retain count, even though self == nil;
}

return self; // self is nil, dealloc can't decrease retain count on _myArray because once you do self.myArray = [NSMutableArray array]; when self is nil, you get dangling pointer and no object has reference to instance of that array, memory leak being end result.


- (void)setMyArray:(NSMutableArray *)inArray
{
    if (_myArray != inArray) 
    {
        [_myArray release];
        _myArray = [inArray retain];
    }
}

If changing to if (self = [super init]) did not fix this leak, the cause lies somewhere else.

TheBlack
  • 1,245
  • 1
  • 8
  • 12
  • 1
    I don't see why if(self = [super init]) should be the reason for the leak detection, thats not how it works. In fact, if(self = [super init]) is perfectly valid, but has probably a different effect than the one the OP intended. – JustSid Jun 02 '11 at 23:58
  • thanks TheBlack - (a) I had to put brackets around the change to silence a warning I was getting when I made this change - I assume this is Ok? (b) when I run instruments after doing this fix & taking the autorelease outagain I get the leak back? I'll put an overview of what I see in Instruments on my post next – Greg Jun 03 '11 at 00:03
  • 1
    I assume you mean you had to do `if ((self = [super init]))`? Yeah, that's the right way to solve that warning. – Steven Fisher Jun 03 '11 at 00:17
  • Forget about warning guys, it doesn't matter. @JustSid check addendum to my answer. – TheBlack Jun 03 '11 at 00:40