在内存警告后释放UIImage时崩溃

时间:2011-02-16 00:21:46

标签: iphone uiimage warnings release dealloc

我对iphone开发很新,我在应用程序中遇到了一个奇怪的崩溃。实际上,在我模拟了内存警告后,我的应用程序总是崩溃。我每次都可以重现这种行为并设法隔离出错误的行:)。

我正在使用自定义UITableViewController,提供自定义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
}
// [...]

这是我为单元格视图设置自定义数据对象的功能:

-(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];
    // [...]

最后,这就是神奇发生的地方:

-(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];
}

因此,在正常情况下一切正常。但是,如果我模拟内存警告,滚动我的UITableView并调用所有这些函数,应用程序崩溃。如果我删除[图像发布],没有崩溃(但'海有内存泄漏')。 NSLog的输出总是类似于:

  

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

我真的看不出我做错了什么,或者我能做些什么来解决这个问题。 这是Xcode调试器的截图...

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

欢迎任何帮助。 提前致谢

编辑1: @bbum Build and Analyze向我展示了一些不相关的警告,但仍然有用。甚至没有看到它在那里

还有另一个我设置图像的地方。在setDataObject中,图片只是一个占位符。我异步启动真实图像的下载,然后在requestDidFinishLoad中恢复。方法如下:

- (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];
}

我使用NSZombie Detection运行仪器,结果似乎指向另一个方向。这是一个截图:

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

我还不太清楚该怎么做,但调查进展如此:)

5 个答案:

答案 0 :(得分:4)

[image release]; // CRASH EXC_BAD_ACCESS

您的回溯表明您使用dealloc UIImage方法崩溃了。你在某处过度释放了image

首先,尝试“构建和分析”,看看它是否会阻止任何有用的警告。修复它们。

接下来,打开Zombie Detection尝试重现问题。它可能提供线索。

请注意,@property“仅仅”方便了声明一个setter / getter方法对(或者其中一个)。无论您使用[foo setImage:bar]还是直接声明方法,@property都是完全等效的。同样,foo.image = bar;[foo setImage:bar];完全相同。

最后,是所有代码处理您的image吗?你如何处理低内存警告?

此外,如果你的二传手致电setNeedsDisplay:,你会感觉更好。使用简单的@property@synthesize setter / getter。然后,当您拨打设定者时,请在该线路上呼叫setNeedsDisplay:。这使得设置UI的业务与确定何时需要显示相隔离。


啊哈!你的僵尸东西非常有用。特别是,您似乎过早地释放了URLConnection,然后导致NSData过早释放。这可能很容易成为问题的根源,或者至少应该在尝试修复问题之前修复。

答案 1 :(得分:1)

尤里卡! 我终于找到了我做错了什么。 当图像被异步加载时,它使用来自用于缓存的自定义对象的数据,存储在缓存管理器中。发出内存警告时,缓存管理器会释放所有内容,从内存中销毁缓存对象。这是我的dealloc在我的“cachable对象”中的样子:

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

是的,我明确地调用了dealloc ...当然,当UIImage想要在数据上发布自己的指针时,它就失败了......

我觉得很蠢^^。 (我总是很难调试自己的程序,因为我有时假设部分代码为“OK”,我甚至不认为看那里......)

底线: NSZombie非常有用(感谢@bbum)找出真正的罪魁祸首。并且从不(?)明确地调用dealloc

(无论如何要“关闭”这个问题?

答案 2 :(得分:0)

您可能正在释放图像以响应内存警告而不将其设置为nil。因此,当您调用setImage:时,您正在释放已释放的对象,因此崩溃。尝试类似:

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

image被声明为属性

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

答案 3 :(得分:0)

专门解决@ sabby的误解:

-(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`
}

最终结果?不保留项集的setter方法。将其与:

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

上述设置器将泄漏传递给init的原始图像,过度释放传递给setImage:的任何图像,如果应用程序存活的时间足够长,很可能会崩溃在:

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

答案 4 :(得分:-1)

这样做,可能会帮助你

-(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];
}

我没有检查过我的自己但是你崩溃的原因是图片的发布。