views:

200

answers:

2

I have the following method in UIImageManipulation.m:

+(UIImage *)scaleImage:(UIImage *)source toSize:(CGSize)size
{
    UIImage *scaledImage = nil;
    if (source != nil)
    {
        UIGraphicsBeginImageContext(size);
        [source drawInRect:CGRectMake(0, 0, size.width, size.height)];
        scaledImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    }
    return scaledImage;
}

I am calling it in a different view with:

imageFromFile = [UIImageManipulator scaleImage:imageFromFile toSize:imageView.frame.size];

(imageView is a UIImageView allocated earlier)

This is working great in my code. I resizes the image perfectly, and throws zero errors. I also don't have anything pop up under build -> analyze. But the second I turn on NSZombieEnabled to debug a different EXC_BAD_ACCESS issue, the code breaks. Every single time. I can turn NSZombieEnabled off, code runs great. I turn it on, and boom. Broken. I comment out the call, and it works again. Every single time, it gives me an error in the console: -[UIImage release]: message sent to deallocated instance 0x3b1d600. This error doesn't appear if `NSZombieEnabled is turned off.

Any ideas?

--EDIT--

Ok, This is killing me. I have stuck breakpoints everywhere I can, and I still cannot get a hold of this thing. Here is the full code when I call the scaleImage method:

-(void)setupImageButton
{
    UIImage *imageFromFile;

    if (object.imageAttribute == nil) {
        imageFromFile = [UIImage imageNamed:@"no-image.png"];
    } else {
        imageFromFile = object.imageAttribute;
    }
    UIImage *scaledImage = [UIImageManipulator scaleImage:imageFromFile toSize:imageButton.frame.size];
    UIImage *roundedImage = [UIImageManipulator makeRoundCornerImage:scaledImage :10 :10 withBorder:YES];
    [imageButton setBackgroundImage:roundedImage forState:UIControlStateNormal];
}

The other UIImageManipulator method (makeRoundCornerImage) shouldn't be causing the error, but just in case I'm overlooking something, I threw the entire file up on github here.

It's something about this method though. Has to be. If I comment it out, it works great. If I leave it in, Error. But it doesn't throw errors with NSZombieEnabled turned off ever.

+2  A: 

The purpose of NSZombieEnabled is to detect messages that get sent to objects after they've been deallocated. The console error you're seeing is NSZombieEnabled telling you that a release message is being sent to a deallocated instance of UIImage. Usually a bug like this is the result of too many calls to release, or not enough calls to retain.

In this case, your scaleImage:toSize: method returns an autoreleased UIImage. The error message you're getting from NSZombieEnabled suggests that you may be releasing this object after it gets returned. This would explain your bug. When your autorelease pool drains it would try to release an object that's already been deallocated.

You're passing imageFromFile to scaleImage:toSize:, and then reassigning that same variable to the return value. There's nothing wrong with this idiom per se, but does require some extra care to avoid memory bugs like this one. You're overwriting your reference to the original object, so you either have to make sure it's autoreleased before the assignment, or save a separate reference that you can manually release after the assignment. Otherwise your original object will leak.

cduhn
But why would this only throw me an error when NSZombieEnabled is set? Shouldn't it throw an error either way if it is releasing an object that is already released? And are you saying it would be better to use something like `UIImage *scaledImage = [ scaleImage:foo...]` and then `[foo release]` immediately after? Still trying to get my head around memory management.
Gordon Fontenot
I've seen cases where sending a message to a deallocated object doesn't result in an EXC_BAD_ACCESS. I'm not clear on exactly why this happens, but it seems like there can be some delay between the time when the runtime executes the dealloc, and the time when the OS actually reclaims the memory. Your example using two variables, `scaledImage` and `foo` looks correct if foo has a retain count of 1 (e.g. if it was created using [[UIImage alloc] init*]). It's worth rereading the Memory Management Programming Guide for Cocoa every week or so until it clicks.
cduhn
Been trying for 2 days now to track this down, but I can't figure it out. Would you mind taking a second look at the code? I've edited it with more info.
Gordon Fontenot
To help narrow it down, set a breakpoint on your call to setBackgroundImage:forState: Then at your gdb prompt in your console, execute "po scaledImage", "po roundedImage", "po imageFromFile", and "po [object imageAttribute]". It will output a description of each object, including its hex memory address (e.g. 0x3b1d600). Then hit continue and look at the object address that's giving you an error from NSZombieEnabled to identify which object is receiving the message after it's been deallocated.
cduhn
One weird thing about your `makeRoundCornerImage:::withBorder:` method is that it returns a retained object when `border` is `YES`, and an autoreleased object when `border` is NO. In this case you're passing YES, so I would expect this to leak memory, but that doesn't explain why NSZombieEnabled is complaining.
cduhn
Found it. Turned out to be a release of the `img` variable being passed into `makeRoundedCornerImage`. Thanks for your help. It got me to go ahead and overhaul that entire `scaleImage` method into a version that was thread-safe.
Gordon Fontenot
A: 

The error was due to a release going on in the makeRoundedCornerImage method from UIImageManipulator. Still not sure why it wasn't getting picked up without NSZombieEnabled turned on, but that's what it was.

You can find the offending line in the Gist I posted in the original question: Line 74.

Gordon Fontenot