views:

455

answers:

5

For some reason, the retain/release behavior in the following code has me completely baffled.

selectedImage = [UIImage imageNamed:@"icon_72.png"];
[selectedImage release];

This should break but does not. Why? I thought imageNamed autoreleased itself which means the release here is redundant and should break when the autorelease occurs.

Here are snippets relevant to selectedImage from the .h and .m files:

@property (nonatomic, readonly) UIImage *selectedImage;
@synthesize delegate, selectedImage, spacerBottom, currentIndex;

Other notes, this does break:

selectedImage = [UIImage imageNamed:@"icon_72.png"];
[selectedImage release];
[selectedImage release];
//objc[55541]: FREED(id): message release sent to freed object=0x59245b0
//Program received signal:  “EXC_BAD_INSTRUCTION”.

As does this:

selectedImage = [UIImage imageNamed:@"icon_72.png"];
[selectedImage release];
[selectedImage autorelease];
//objc[55403]: FREED(id): message autorelease sent to freed object=0x59b54c0
//Program received signal:  “EXC_BAD_INSTRUCTION”.

And so does the following:

selectedImage = [UIImage imageNamed:@"icon_72.png"];
[selectedImage autorelease];
[selectedImage release];
//objc[55264]: FREED(id): message release sent to freed object=0x592c9a0
//Program received signal:  “EXC_BAD_INSTRUCTION”.

And so does this:

selectedImage = [UIImage imageNamed:@"icon_72.png"];
[selectedImage autorelease];
[selectedImage autorelease];
//objc[55635]: FREED(id): message release sent to freed object=0x5b305d0
//Program received signal:  “EXC_BAD_INSTRUCTION”.
+6  A: 

Odd and wierd, yes. But not completely inexplicable. this is what I think is happening.

You're correct; imageNamed: returns an autoreleased object. this means that it's going to get released sometime in the future so you calling release on it straight away won't cause an error - release isn't psychic, it doesn't know that an autorelease pool is also going to release it!

If you left your code running the autorelease pool will eventually try to release it again and then you will get the error you're expecting.

You've actually answered our own question - you say 'should break when the autorelease occurs' which is absolutely correct, when the autorelease occurs, it will break :)

The other examples break because you're forcing releases to happen by either calling them directly or doing enough stuff that the autorelase pool is triggered to run and calls release for you. (You can't predict when the autorelease pool will run, you can just know that at some point in your run loop, autoreleased things maight be released.)

deanWombourne
I was under the impression that the autorelease pool will drain once the program returns to the event loop. Is this not correct? I can leave the program open for a while and never see it crash.
MrHen
Yes the autorelease pool will never keep stuff around for "very long". True, there's no guarantee for when it will be drained, but it's not something you have to leave your program running for a while to discover. Jasarien's answer gives a correct explanation of what happens in this special case.
Felixyz
+2  A: 

-imageNamed: returns an autoreleased image, which, as deanWombourne says, will be autoreleased at some time in the future (the exact time is undefined).

The reason it's not being autoreleased as early as you are perhaps used to is that -imageNamed also caches the image it returns. The cache is retaining the image.

So essentially, the retain cycle is something like this:

  • -imageNamed: called,
    • System allocs and init's an image -- retain count = 1;
    • System caches image -- retain count = 2;
    • System autoreleases image and returns to you -- retain count = 1; (theoretically, the image still has retain count of 2, because the auto release pool has not yet released it).
  • you call release on the image -- retain count should be 0 and the object should be deallocated.
  • At some point in the future (at the end of the run loop), the auto release pool should release the image, and will crash because you have over released it.

If you do not release it, the cache will continue to retain the image until it releases it, for instance when a memory warning occurs. So when you get an image using imageNamed, it doesn't get deallocated, until the cache is purged.

Hope this clears things up.

Jasarien
Ah, the caching bit is a good tip. Is there somewhere I can learn more about how the caching affects the retain count? Is it possible that the cache could keep the object around even if my program is no longer retaining the UIImage object?
MrHen
You shouldn't concern yourself with the actual value of the retain count. All you should care about is that the object you receive from a convenience method is not retained by you, so you shouldn't release it.If you don't want the images to be cached for any reason, you should use an alternative method of creating them, for instance, -imageWithContentsOfFile: doesn't cache the image (stated in the documentation for that method). You can expect the image object returned from imageWithContentsOfFile: to be autoreleased and not cached and it will be deallocated at the end of the run loop.
Jasarien
"and it will be deallocated at the end of the run loop." - providing you're not retaining it at all, (ie, setting it as an imageView's image, adding it to an array, etc).
Jasarien
@Jasarien I am not really concerned about the retainCount. I just thought the behavior was weird. I will switch the implementation to imageWithContentsOfFile and see what happens. If I understand you correctly, there should be no caching and, therefore, no retain due to cache. I expect this would cause an error similar to the other examples. But that will have to wait until Monday. Have a good weekend! :)
MrHen
A: 

You say that "This should break"

selectedImage = [UIImage imageNamed:@"icon_72.png"];
[selectedImage release];

You are wrong.

It probably would break if UIImage was an instance of the kind of Class you and i would write and learnt to write from our Cocoa books, but we didn't write it so we shouldn't guess at it's implementation.

How UIImage works is an implementation detail and not your concern. All you know is that you should be able to EXPECT it to work if you follow the rules, which i believe are now being referred to as NARC, and which you haven't done here. Nowhere are objects guaranteed to 'break' if you use them incorrectly. You cannot count on Objects being deallocated when you are thru with them - that isn't part of the memory management contract.

Not all of Apple's objects work like text book class/instances - in reality, objects may be cached, reused, recycled or not even be objects at all.

Don't worry about it, follow the rules.

mustISignUp
I need the implementation details because the app I am working on is pushing the memory limits of the iPhone/iPad. The reason I ran into this behavior is because the UIImage objects are acting differently than I expected. The information provided in the other answers lets me know how to deal with UIImage in a more intelligent manner.
MrHen
Sorry, but these guy are wrong. It is nothing to do with autorelease or retain-cycles. IF the frameworks have cached the image, they will un-cache it when done with it, and then you will inexplicably crash at some random point because you 'intelligently' dealt with it.
mustISignUp
I resent you saying I'm wrong ;)You are correct in that the implementation details are not to be worried about, but in this particular case, even if not directly, the problem **is** to do with retain counts because the cache is retaining the image.
Jasarien
@Jasarien I don't mean it personally but the image is not going to be released at the 'end of the run-loop', i think @MrHen would have noticed it - as normally the end of the current run loop cycle is not tooooo far in the distant future.
mustISignUp
A: 

When I create this category:

@implementation UIImage (ReleaseChecks)

+ (id)allocWithZone:(NSZone *)zone
{
    id o = [super allocWithZone:(NSZone *)zone];
    NSLog(@"Image post-ALLOC: 0x%x",
                (unsigned int)o );
    return o;
}

- (id)autorelease
{
    NSLog(@"Image pre-AUTORELEASE: 0x%x; Retain Count %u",
                (unsigned int)self,
                (unsigned int)[self retainCount]
                );
    return [super autorelease];
}

- (void)release
{
    NSLog(@"Image pre-RELEASE: 0x%x\n; Retain Count %u",
                (unsigned int)self,
                (unsigned int)[self retainCount]
                );
    [super release];
}

- (void)dealloc {
    NSLog(@"Image pre-DEALLOC: 0x%x\n; Retain Count %u",
                (unsigned int)self,
                (unsigned int)[self retainCount]
                );
    [super dealloc];
}

It appears that -autorelease is not called when allocated with +imageNamed:.

However, when I have created a whole bunch of these with +imageNamed: and then later get a memory warning, I can see them all release and dealloc. This was tested on iPhone Simulator 4.0.

A: 

I also have a problem with a huge number of images in my iPad app. How do you solve the problem? Just using the [uiimage imagewithdata:] instead of [UIImage imageNamed:]?

slatvick