Crash when releasing UIImage after memory-warning

1.8k views Asked by At

I am quite new to the iphone development and I'm encountering a weird crash in my application. Indeed, my application always crashes after I've simulated a memory warning. I can reproduce this behavior every time and have managed to isolate the faulty line :).

I'm working in a custom UITableViewController, delivering custom UITableViewCells.

@implementation CustomTableViewController
// [...]
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{   
static NSString *CellIdentifier = @"MyTableViewCell";
UITableViewCell *cell = nil;

if ([indexPath row] < [dataList childCount])
{
    cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier];

    if (nil == cell) 
    {
        cell = [[[KpowUITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                          reuseIdentifier:CellIdentifier] autorelease];

        KUICustomView* customView = [[KUICustomView alloc]initWithFrame:CGRectZero];
        [(KpowUITableViewCell*)cell setFrontView:customView];
        [customView release];
    }

    KUICustomView* cView = [(KpowUITableViewCell*)cell frontView];
    [cView setDataObject:[dataList getChildAtIndex:[indexPath row]]]; // The crash happens in this function
}
// [...]

Here's the function where I set the custom data object for my cell view :

-(void)setDataObject:(DataObject *)do
{
    [do retain];
    [dataObject release];
    dataObject = do;

    NSString* defaultPath = [NSString stringWithFormat:@"%@/default_image.png", [[NSBundle mainBundle] resourcePath]];
    UIImage* defaultImage = [[UIImage alloc] initWithContentsOfFile:defaultPath];
    [self setImage: defaultImage];//[UIImage imageNamed:@"default_image"]]; // The crash happens in this function
    [defaultImage release];
    // [...]

And finally, here's where the magic happens :

-(void)setImage:(UIImage *)img
{
    [img retain];
    NSLog(@"setImage : old image > %@/%@/%i", [image description],         [[image class]description], [image retainCount]);
    [image release]; // CRASH EXC_BAD_ACCESS
    image = img;

    [self setNeedsDisplay];
}

So, everything works just fine in a normal scenario. But if I simulate a memory warning, scroll my UITableView and all these functions get called, the application crashes. If I remove the [image release], no crash (but 'Hai there memory leaks'). The output of the NSLog is always something like :

setImage : old image > <UIImage: 0x4b54910>/UIImage/1

I really can't see what I'm doing wrong, or what I could do to work around this issue. Here's a screenshot of Xcode debugger...

http://img30.imageshack.us/i/debuggerscreen.png/

Any help is welcome. Thanks in advance

Edit 1: @bbum Build and Analyze showed me some unrelated warnings, but still useful. Didn't even see it was there

There is one other place where I set the image. In the setDataObject, the image is just a placeholder. I launch the download of the real image asynchronously, and get it back in requestDidFinishLoad. The method goes like this :

- (void)requestDidFinishLoad:(KURLRequest*)request
{
    if (request == currentRequest) 
    {
        UIImage* img = [[UIImage alloc] initWithData:[request data]];

        if (nil != img)
            [self setImage:img];

        [img release];
    }

    if (currentRequest == request)
        currentRequest = nil;
    [request release];
}

I runned instruments with NSZombie Detection, and the result seems to point in another direction. Here's a screenshot :

http://img13.imageshack.us/i/zombieinstrument.jpg/

I'm not quite sure what to do with that yet, but the investigation progresses :)

5

There are 5 answers

1
kombucha On BEST ANSWER

Eureka! I finally found what I did wrong. When the image is asynchronously loaded, it uses data coming from a custom object used for caching, stored in a cache manager. When a memory warning is issued, the cache manager releases everything, destroying the cache object from memory. Here's what my dealloc looked like in my "cachable object" :

-(void)dealloc
{
    // [...]
    [data dealloc];
    // [...]
}

Yeah, I was explicitly calling dealloc... So of course when UIImage wanted to release its own pointer on the data, it failed...

I feel so stupid ^^. (I'm always having a hard time debugging my own programs, since I sometimes assume parts of code as "OK", and I don't even think to look there...)

Bottom line : NSZombie was really useful (thanks @bbum) to find out the real culprit. And never (?) call dealloc explicitly.

(is there anyway to "close" this question?

4
bbum On
[image release]; // CRASH EXC_BAD_ACCESS

Your backtrace shows that you are crashing in the dealloc method of UIImage. You have over-released image somewhere.

First, try "Build and Analyze" and see if it barfs up any useful warnings. Fix them.

Next, turn on Zombie Detection try to reproduce the problem. It may provide clues.

Note that @property is "merely" convenience for declaring a setter/getter method pair (or one of 'em). [foo setImage:bar] is exactly equivalent regardless of whether you use @property or declare the method directly. Similarly foo.image = bar; is exactly the same as [foo setImage:bar];.

Finally, is that all the code that handles your image? How do you handle the low memory warning?

Also, you'd be better off if your setter did not call setNeedsDisplay:. Use a simple @property and @synthesize the setter/getter. Then, when you call the setter, call setNeedsDisplay: on the line after. This keeps the business of setting up the UI isolated from determining when the display needs to happen.


Aha! Your zombie stuff was very useful. In particular, it looks like you are prematurely releasing an URLConnection, which then causes an NSData to be released too soon. This could easily be the source of your problem or, at the least, should be fixed before trying to fix this issue.

7
Sabby On

Do in this way, Might it help you

-(void)setImage:(UIImage *)img
{
    [img retain];
    NSLog(@"setImage : old image > %@/%@/%i", [image description],         [[image class]description], [image retainCount]);
     // CRASH EXC_BAD_ACCESS
    image = img;
if(image)
{
[image release];
}

    [self setNeedsDisplay];
}

I haven't checked it my self but the reason for your crashing is the release of image.

1
Robert Höglund On

You are probably releasing the image in response to the memory warning without setting it to nil as well. So when you are calling setImage: you are releasing an already released object, hence the crash. Try something like:

- (void)viewDidUnload {
    [super viewDidUnload];
    [image release]; image = nil;
}

or if image is declared as a property

- (void)viewDidUnload {
    [super viewDidUnload];
    self.image = nil;
}
0
bbum On

Specifically to address @sabby's misconception:

-(void)setImage:(UIImage *)img
{
    [img retain]; // img rc +1
    image = img; 
    if(image)
    {
        [image release]; // img rc -1
    }
    // image set, but not retained by `self`
}

End result? A setter method that does not retain the item set. Combine that with:

- initWithImage:anImage
{
     if (self=[super init]) {
           image = [anImage retain];
     }
     return self;
}

The above setter will leak the original image passed to init, over-release any image passed to setImage: and, if the app survives long enough, quite likely crash in:

- (void) dealloc
{
    [image release];
    [super dealloc];
}