我遇到的问题是,相对较大的图像似乎永远不会从内存中释放出来(大小为1MB~5MB)。当用户滚动浏览一组图像时,将调用以下代码块。大约15张图像后,应用程序将崩溃。有时会调用“didReceiveMemoryWarning”,有时它不会被调用 - 应用程序只会崩溃,停止,退出调试,而不会停止任何代码行 - 没有。我假设当设备内存不足时会发生这种情况?另一个问题是'dealloc'似乎永远不会被称为子类'DownloadImageOperation'。有什么想法吗?
获取和设置图像:
//Calling this block of code multiple times will eventually cause the
// application to crash
//Memory monitor shows real memory jumping 5MB to 20MB increments in instruments.
//Allocations tool shows #living creeping up after this method is called.
//Leaks indicate something is leaking, but in the 1 to 5 kb increments. Nothing huge.
DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:imageFilePath];
[imageOp setCompletionBlock:^(void){
//Set the image in a UIImageView in the open UIViewController.
[self.ivScrollView setImage:imageOp.image];
}];
//Add operation to ivar NSOperationQueue
[mainImageQueue addOperation:imageOp];
[imageOp release];
DownloadImageOperation定义:
.h文件
#import <Foundation/Foundation.h>
@interface DownloadImageOperation : NSOperation {
UIImage * image;
NSString * downloadURL;
NSString * downloadFilename;
}
@property (retain) UIImage * image;
@property (copy) NSString * downloadURL;
@property (copy) NSString * downloadFilename;
- (id) initWithURL:(NSString *)url localPath:(NSString *)filename;
@end
.m文件
#import "DownloadImageOperation.h"
#import "GetImage.h"
@implementation DownloadImageOperation
@synthesize image;
@synthesize downloadURL;
@synthesize downloadFilename;
- (id) initWithURL:(NSString *)url localPath:(NSString *)filename {
self = [super init];
if (self!= nil) {
[self setDownloadURL:url];
[self setDownloadFilename:filename];
[self setQueuePriority:NSOperationQueuePriorityHigh];
}
return self;
}
- (void)dealloc { //This never seems to get called?
[downloadURL release], downloadURL = nil;
[downloadFilename release], downloadFilename = nil;
[image release], image = nil;
[super dealloc];
}
-(void)main{
if (self.isCancelled) {
return;
}
UIImage * imageProperty = [[GetImage imageWithContentsOfFile:downloadFilename andURL:downloadURL] retain];
[self setImage:imageProperty];
[imageProperty release];
imageProperty = nil;
}
@end
获取图像类
.m文件
+ (UIImage *)imageWithContentsOfFile:(NSString *)path andURL:(NSString*)urlString foundFile:(BOOL*)fileFound {
BOOL boolRef;
UIImage *image = nil;
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
if (image==nil) {
boolRef = YES;
image = [UIImage imageWithContentsOfFile:[[AppDelegate applicationImagesDirectory] stringByAppendingPathComponent:[path lastPathComponent]]];
}
if (image==nil) {
boolRef = YES;
image = [super imageWithContentsOfFile:path];
}
if (image==nil) {
//Download image from the Internet
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setTimeOutSeconds:120];
[request startSynchronous];
NSData *responseData = [[request responseData] retain];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
NSData *rdat = [[NSData alloc] initWithData:responseData];
[responseData release];
NSError *imageDirError = nil;
NSArray *existing_images = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[path stringByDeletingLastPathComponent] error:&imageDirError];
if (existing_images == nil || [existing_images count] == 0) {
// create the image directory
[[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:NO attributes:nil error:nil];
}
BOOL write_success = NO;
write_success = [rdat writeToFile:path atomically:YES];
if (write_success==NO) {
NSLog(@"Error writing file: %@",[path lastPathComponent]);
}
image = [UIImage imageWithData:rdat];
[rdat release];
}
return image;
}
为这个巨大的代码块道歉。我真的不知道问题可能在哪里,所以我试图尽可能包容。谢谢你的阅读。
答案 0 :(得分:9)
操作未被释放的主要问题是您有一个保留周期,由完成块中imageOp
的引用引起。考虑一下你的代码:
DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:imageFilePath];
[imageOp setCompletionBlock:^(void){
//Set the image in a UIImageView in the open UIViewController.
[self.ivScrollView setImage:imageOp.image];
}];
在ARC中,您可以为操作添加__weak
限定符,并在imageOp
中使用completionBlock
而不是__block
,以避免强引用周期。在手动引用计数中,您可以通过使用imageOp
限定符来避免保留周期,以实现相同的目的,即阻止块保留DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:filename];
__block DownloadImageOperation *blockImageOp = imageOp;
[imageOp setCompletionBlock:^(void){
//Set the image in a UIImageView in the open UIViewController.
[self.imageView setImage:blockImageOp.image];
}];
:
completionBlock
我认为如果您这样做,您会看到您的操作正确发布。 (请参阅Transitioning to ARC Release Notes中的“使用终身限定符以避免强引用周期”部分。我知道您没有使用ARC,但本节介绍了ARC和手动引用计数解决方案。)
如果您不介意,我对您的代码有其他意见:
您不应该从DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:filename];
__block DownloadImageOperation *blockImageOp = imageOp;
[imageOp setCompletionBlock:^(void){
//Set the image in a UIImageView in the open UIViewController.
UIImage *image = blockImageOp.image;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.imageView setImage:image];
}];
}];
更新UI而不将其分发到主队列...所有UI更新都应该在主队列上进行:
init
您在UIScrollViewDelegate
方法中使用了访问器方法。作为一个良好的实践,你真的不应该。请参阅高级内存管理编程指南中的Don’t Use Accessor Methods in Initializer Methods and dealloc。
虽然我们可能已经解决了操作未被释放的问题,但我怀疑除非您已经编码了NSOperation
次调用以释放已从可见屏幕滚动的图像,否则将继续有记忆问题。话虽如此,您可能已经解决了这个问题,如果是这样,我甚至为提及它而道歉。我只是提出这个问题,因为它很容易解决这个NSOperation
问题,但忽略了滚动视图在屏幕滚动时释放图像。
我不确定您的子类NSOperation
是否支持并发性,因为您缺少并发编程指南中Defining a Custom Operation中讨论的一些关键方法。也许你已经这样做了,但为了简洁起见省略了它。或者,我认为如果您使用其中一个现有的NSBlockOperation
类(例如maxConcurrentOperationCount
)来处理这些内容会更容易。你的电话,但是如果你追求并发,你需要确保将队列的retain
设置为合理的,例如4。
您的代码有一些冗余的release
语句。话虽如此,你也有必要的{{1}}陈述,所以你已经确保你不会有问题,但这只是有点好奇。显然,ARC让你摆脱了那种杂草,但我很欣赏这是一个很大的进步。但是当你有机会的时候,请看看ARC,因为它可以让你免于担心这一点。
您可能应该通过静态分析器运行代码(“产品”菜单上的“分析”),因为您有一些死商店等。