4

I am running this code with no autorelease pool in place under ARC:

- (NSString*) outName {
    if (!outName) {
        outName = [[NSString alloc] initWithFormat:@"whatever"]; // or stringWithFormat
    }
    return outName;
}

The debugger says that it's leaking the single outName instance each time with no pool in place.

This does not happen if I change the code to

- (NSString*) outName {
    if (!outName) {
        outName = @"whatever";
    }
    return outName;
}

Which I cannot do (this example is obviously simplified). Also, the leak message disappears if I create an autorelease pool in the calling code (which I would like to avoid).

Why is ARC insisting on autoreleasing this object, which is held in a strong property? And more importantly, how can I avoid this warning?

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
  • 1
    Why would you assume it's _Demanding_ an autoreleasepool? – CodaFi Jun 25 '12 at 14:45
  • 2
    ARC doesn't remove `retain` and `release`, it just removes the need for YOU to use `retain` and `release`. Without an autoreleasepool, the items are leaking. (moved this to a comment since I'm not 100% sure, I'll undelete my answer if people agree with the comment) – James Webster Jun 25 '12 at 14:45
  • If this is basically a getter, why not simply return outName and let the setter worry about memory management? – CodaFi Jun 25 '12 at 14:54
  • @CodaFi First: I'm getting "__NSCFString autoreleased with no pool in place - just leaking"... I could ignore this, of course, since it's essentially a readonly instance, but if the holding object deallocs, the object would indeed be leaked. – Dan Rosenstark Jun 25 '12 at 14:56
  • @CodaFi problem persists even if I create the string elsewhere and remove ALL logic from the getter. I know where the problem is because I'm breaking on `objc_autoreleaseNoPool` – Dan Rosenstark Jun 25 '12 at 15:02
  • The reason I say this is, getters aren't ever supposed to set anything. They are merely to read the value of the instance. – CodaFi Jun 25 '12 at 15:26
  • @CodaFi putting a lazy load in the getter is a common design pattern. http://stackoverflow.com/questions/2026035/lazy-loading-in-objective-c – Dan Rosenstark Jun 25 '12 at 15:36
  • 1
    @JamesWebster that answer IS more appropriate as a comment, thanks. My question was why ARC chooses an autorelease strategy where a human would just return the strongly-held ivar without doing a `retain] autorelease]`. I guess the answer is "safety." The method that does this is, apparently, `objc_retainAutoreleaseReturnValue` – Dan Rosenstark Jun 25 '12 at 21:48

4 Answers4

2

This is a question of ownership.

Lets talk about the NSString that you allocated yourself first. When you allocate an object, memory in the heap is reserved for that object (unless you allocWithZone: to another location). The retain count is implicitly 1 and you own the object, i.e. you are responsible for releasing it when you are done. If you are going to return a pointer to that object, i.e. returning that object, you don't completely give up responsibility of ensuring that object doesn't leak. You can't release it, because the retain count will go to 0 and that object will be dealloced. You autorelease it, ensuring that at the end of your run loop (or sooner) the object will be released and possibly dealloced. The calling function is responsible for retaining the returned object if the returned object needs to survive longer.

Without an autorelease pool, you will leak because the designated autoReleasePool is null (remember its fine to message null, which is why this doesn't just crash instead of just leaking).

The example with a fixed @"whatever" doesn't leak because the compiler reserves program memory for that string, and -release's have no effect on them. The same is true for some low value NSNumbers as well.

As James said, ARC doesn't remove the retain release and autorelease concepts.

EDIT: How is outName declared as an ivar/property?

Jared Kipe
  • 1,189
  • 6
  • 5
  • `outName` was originally declared as `readonly` and as a `__strong` iVar, but now it's a `strong` property instead. Also I've put the setter somewhere else entirely, which (as you'll have guessed) doesn't change anything. – Dan Rosenstark Jun 25 '12 at 15:08
  • +1 great explanation. So, now way to avoid this aside from creating an autorelease pool? – Dan Rosenstark Jun 25 '12 at 15:10
  • Are you using @synthesize or @dynamic? – Jared Kipe Jun 25 '12 at 15:11
  • I was originally not using anything at all (readonly, __strong iVar and a getter method) but now I'm using `@synthesize` – Dan Rosenstark Jun 25 '12 at 15:14
  • 1
    NVM, ARC will insert the function objc_retainAutoreleaseReturnValue(outName) before the return of any object. – Jared Kipe Jun 25 '12 at 15:19
  • 1
    With compiler optimizations turned on the runtime may look up the stack and remove the retain call (objc_retain()) in the calling code and the objc_retainAutoreleaseReturnValue() call in the method before it returns so that it doesn't waste time with the autorelease. Bottom line, you need to have an NSAutoreleasePool in place for proper Objective-C memory management. A lot of stock Apple code relies on autoreleases. And the memory management would be much more confusing without it. – Jared Kipe Jun 25 '12 at 15:25
  • Thanks Jared for the discussion etc. The answer, in my case, is to push the USE of the property down to the class that has direct access to the ivar. In this case, I'm just doing a boolean check with it, so I put that boolean check on the object that can touch the ivar directly and the problem disappears. Not sure if you want to work that into your answer somehow, or not, but either way, great help, thanks and best of luck here on SO. – Dan Rosenstark Jun 25 '12 at 15:29
1

When ARC returns the ivar (or any object) to the calling method, it has to guarantee that it won't be released before the current RunLoop is finished. As a programmer you know that it will never be released, but ARC relies on algorithmic guarantees (best practices). ARC will call retain] autorelease] unless the variable points to a constant (not instantiated with NARC), and is therefore not at risk for being released.

You should not avoid this warning. You need to fix your code. You can add an autorelease pool. If you do not wish to do this, the other way is to push the logic that uses the ivar down to the object that is actually holding the ivar.

Community
  • 1
  • 1
Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
0

Jared's answer is good, but part of the way ARC works is by using naming conventions. a method name of outName implies that it returns an autoreleased value, so if there was a de-ARC-ifier, your last line would look like this:

return [[outName retain] autorelease];

Obviously this needs an autorelease pool.

This doesn't happen in your second example because you're returning a constant, so the retain/autorelease gets optimised away.

Community
  • 1
  • 1
Stephen Darlington
  • 51,577
  • 12
  • 107
  • 152
  • Sorry, I'm not following why you say I'm not using a `strong` property. `self.outName = ` and `outName =` are the same under ARC. Both would increase the retain count of the object by 1 since the ivar ITSELF is `__strong`. – Dan Rosenstark Jun 25 '12 at 15:20
  • Because he is not using his property's getter and setter, in fact he is overriding the getter. – Jared Kipe Jun 25 '12 at 15:26
  • @JaredKipe You're right about the second point; I'll remove that reference. – Stephen Darlington Jun 25 '12 at 15:35
  • @Yar Sorry, didn't notice that you were overriding the getting. Still, the point about naming conventions stands. I've updated my answer to make that clearer. – Stephen Darlington Jun 25 '12 at 15:39
  • Thanks, I've worked out a solution by pushing the use of the ivar down to the class that can actually access it. However, I'll check out the naming convention point and see if I can get any traction that way. – Dan Rosenstark Jun 25 '12 at 15:53
  • @StephenDarlington so what kind of method name would NOT produce the autorelease statement? (I'm suspecting that method names wouldn't change this). – Dan Rosenstark Jul 09 '12 at 04:58
  • @Yar I believe `copyName` and `newName` would return retained objects (almost certainly the former, possibly the latter). – Stephen Darlington Jul 16 '12 at 11:17
  • @StephenDarlington just for the sake of argument, I disagree that names make a difference. Is there a test we can think of, documentation you can point to, or should I open up an SO question on this (risky since it might get no traffic)? – Dan Rosenstark Jul 16 '12 at 14:55
  • @Yar Apple make a big point about ARC using Cocoa conventions. Regardless of whether it is enforced or not you should be using the Frameworks naming conventions which, in this case, means that `outName` returns an autoreleased value. As for how to check... not sure. Maybe look at the compiled code (in Xcode) and see the calls to retain/release/autorelease? – Stephen Darlington Jul 16 '12 at 15:05
  • Also, according to the Transitioning to ARC Release Notes I was wrong, you "cannot give an accessor a name that begins with new". – Stephen Darlington Jul 16 '12 at 15:08
  • Hmmm... alright, I'll leave this alone for now, since I should be focusing on making rain. Thanks for the dialogue. – Dan Rosenstark Jul 16 '12 at 15:52
-1

Weird, I have a very similar method that I use and it doesn't leak. It looks like this:

-(NSString *) dataFilePath {

    NSString *appendPath;
    if (isPowerMode==YES) {
        appendPath = kDataFileNamePower;
    } else {
        appendPath = kDataFileNameClassic;
    }

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    return [documentsDirectory stringByAppendingPathComponent:appendPath];
}       

So maybe going from there, you'd want to declare an empty NSString pointer at the beginning of the method and fill and return it instead of outName.

Kaan Dedeoglu
  • 14,765
  • 5
  • 40
  • 41