在NSOperation中设置UIImage时内存泄漏

时间:2013-02-04 22:28:14

标签: ios xcode memory-management memory-leaks

我遇到的问题是,相对较大的图像似乎永远不会从内存中释放出来(大小为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;
}

为这个巨大的代码块道歉。我真的不知道问题可能在哪里,所以我试图尽可能包容。谢谢你的阅读。

1 个答案:

答案 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,因为它可以让你免于担心这一点。

  • 您可能应该通过静态分析器运行代码(“产品”菜单上的“分析”),因为您有一些死商店等。