内存警告后释放 UIImage 时崩溃
我对 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您的回溯显示您在
UIImage
的dealloc
方法中崩溃。您在某处过度释放了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 过早释放。这很容易成为问题的根源,或者至少应该在尝试解决此问题之前解决。
Your backtrace shows that you are crashing in the
dealloc
method ofUIImage
. You have over-releasedimage
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. Similarlyfoo.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, callsetNeedsDisplay:
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.
尤里卡!
我终于发现我做错了什么。
当图像异步加载时,它使用来自用于缓存的自定义对象的数据,该对象存储在缓存管理器中。当发出内存警告时,缓存管理器会释放所有内容,从内存中销毁缓存对象。这是我的 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" :
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?
您可能会释放图像以响应内存警告,而不将其设置为 nil。因此,当您调用
setImage:
时,您正在释放一个已经释放的对象,因此会发生崩溃。尝试类似:或者如果
image
被声明为属性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:or if
image
is declared as a property特别是为了解决@sabby的误解:
最终结果?不保留项集的 setter 方法。结合起来:
上面的 setter 将泄漏传递给
init
的原始图像,过度释放传递给setImage:
的任何图像,并且,如果应用程序存活足够长的时间,很可能会崩溃:Specifically to address @sabby's misconception:
End result? A setter method that does not retain the item set. Combine that with:
The above setter will leak the original image passed to
init
, over-release any image passed tosetImage:
and, if the app survives long enough, quite likely crash in:这样做,可能会对你有帮助,
我自己没有检查过,但你崩溃的原因是图像的释放。
Do in this way, Might it help you
I haven't checked it my self but the reason for your crashing is the release of image.