内存警告后释放 UIImage 时崩溃

Posted

技术标签:

【中文标题】内存警告后释放 UIImage 时崩溃【英文标题】:Crash when releasing UIImage after memory-warning 【发布时间】:2011-02-16 00:21:45 【问题描述】:

我对 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 并调用所有这些函数,应用程序就会崩溃。如果我删除 [image release],则不会崩溃(但是'Hai there memory leaks')。 NSLog 的输出总是类似于:

setImage : old image &gt; &lt;UIImage: 0x4b54910&gt;/UIImage/1

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

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

欢迎任何帮助。 提前致谢

编辑 1: @bbum 构建和分析向我显示了一些不相关的警告,但仍然有用。甚至没看到它在那里

我在另一个地方设置了图像。在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/

我还不太清楚该怎么做,但调查正在进行中:)

【问题讨论】:

保留计数永远不能为零。 retainCount 没用。 @bbum 我知道我不能信任retainCount,但在某些时候我愿意尝试任何事情......任何事情:p 【参考方案1】:
[image release]; // CRASH EXC_BAD_ACCESS

您的回溯显示您在UIImagedealloc 方法中崩溃。你在某处过度释放了image

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

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

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

最后,all 是处理您的image 的代码吗?如何处理内存不足警告?

另外,如果你的二传手调用setNeedsDisplay:,你会更好。使用简单的@property@synthesize setter/getter。然后,当你调用setter时,调用setNeedsDisplay:就行了。这使得设置 UI 的业务与确定何时需要显示无关。


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

【讨论】:

我不知道您是否在我修改帖子时收到通知(请参阅编辑 1)。我也没有在我的控制器中做任何关于内存警告的事情,只是默认实现。我确实清空了一些缓存(一个存储用于图像的数据,因此它可能是相关的)。我不喜欢属性的“点”符号^^。看起来你只是在分配一些东西,而实际上可能会有更多事情发生(因为它是一种方法)。无论如何,谢谢你的帮助。 是的——我看到了你的通知。点符号是一种接受或离开它的东西。如果你愿意,你可以离开它。 :) 您添加的代码调用setMoviePosterImage:,而不是setImage:。前者叫后者吗? setMoviePosterImage:setImage。我只是在第一次发布它时重命名它,以使其更加清晰和与我的问题相关。看起来我的结果正好相反。 哎呀,我没有看到你编辑了你的答案。它本来可以为我节省几个小时>_ 【参考方案2】:

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

-(void)dealloc

    // [...]
    [data dealloc];
    // [...]

是的,我明确地调用了dealloc...当然,当UIImage想要释放它自己的数据指针时,它失败了...

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

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

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

【讨论】:

通常,您应该接受最有帮助的答案,然后将您的解决方案添加到您的问题中(感谢您添加最终解决方案 - 总是对偶然发现您的问题的其他人有所帮助问题!)【参考方案3】:

您可能在没有将其设置为 nil 的情况下释放图像以响应内存警告。因此,当您调用 setImage: 时,您正在释放一个已经释放的对象,因此会崩溃。尝试类似:

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

或者如果image 被声明为属性

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

【讨论】:

UITableViewCell 不是UIViewController,所以没有viewDidUnload 方法:/。而且我没有在我的CustomTableViewController 中实现它。图像也不是属性,只有二传手。所以它不能从外部以任何其他方式进行修改(至少这是我的理解)。【参考方案4】:

专门解决@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];

【讨论】:

【参考方案5】:

这样做,可能对你有帮助

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

我自己没有检查过,但是你崩溃的原因是图像的释放。

【讨论】:

是的,但如果我这样做,旧图像永远不会被释放,新图像不会被保留(连续调用保留和释放)。 这个建议保证image在调用setImage:....之后是一个悬空指针。 @Kombucha,检查是否存在图像对象,否则不释放它。因此,只有当对象指向时才会释放图像,而且如果您的图像对象是全局的,这是正确的方法,你也可以在dealloc方法中释放它。尝试做一些内存管理..... @bbum ,这是正确的方法............由于图像对象的提前释放,应用程序正在崩溃,并且条件良好 不,不是。 if (image) [image release]; 从不是正确的做法(即使在维护弱引用时也不行)。最后,img 的保留计数没有改变,这意味着 image 很可能会从对象下被释放。

以上是关于内存警告后释放 UIImage 时崩溃的主要内容,如果未能解决你的问题,请参考以下文章

从相机点击多张图像时收到内存警告

iOS:ARC,不释放内存

合并两个 UIImage 时收到内存警告

内存警告后在 iOS 上运行 OpenGL 崩溃

iOS4 调用 ImageNamed:仍然泄漏或导致内存问题?

使用后释放 UIImage