内存警告后释放 UIImage 时崩溃

发布于 2024-10-17 17:18:50 字数 3188 浏览 2 评论 0原文

我对 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:旧图像>/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 检测运行仪器,结果似乎指向另一个方向。这是屏幕截图:

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

我还不太确定该怎么办,但调查正在进行:)

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

浅笑依然 2024-10-24 17:18:51
[image release]; // CRASH EXC_BAD_ACCESS

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

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

接下来,打开僵尸检测尝试重现问题。它可能会提供线索。

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

最后,这就是处理您的图像全部代码吗?您如何处理内存不足警告?

另外,如果您的 setter 没有调用 setNeedsDisplay:,情况会更好。使用简单的 @property@synthesize setter/getter。然后,当您调用 setter 时,在后面的行中调用 setNeedsDisplay: 。这使得设置 UI 的业务与确定何时需要进行显示的业务无关。


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

[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.

转身泪倾城 2024-10-24 17:18:51

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

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

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

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

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

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

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?

清醇 2024-10-24 17:18:51

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

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

或者如果 image 被声明为属性

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

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;
}
失眠症患者 2024-10-24 17:18:51

特别是为了解决@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;
}

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

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

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];
}
感性 2024-10-24 17:18:51

这样做,可能会对你有帮助,

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文