内存警告后释放 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 > <UIImage: 0x4b54910>/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
您的回溯显示您在UIImage
的dealloc
方法中崩溃。你在某处过度释放了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 时崩溃的主要内容,如果未能解决你的问题,请参考以下文章