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 :)
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" :
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?