2

我遇到了一个问题,相对较大的图像似乎永远不会从内存中释放(大小为 1MB~5MB)。当用户滚动一组图像时,会调用以下代码块。大约 15 张图像后,应用程序将崩溃。有时会调用“didReceiveMemoryWarning”,有时不会调用——应用程序只会崩溃、停止、退出调试,并且不会在任何代码行上停止——什么都没有。我认为这是设备内存不足时会发生的情况?另一个问题是子类“DownloadImageOperation”似乎从未调用过“dealloc”。有任何想法吗?

获取和设置图像:

//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];

下载图像操作定义:

.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;
}

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

4

1 回答 1

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

我想如果你这样做,你会看到你的操作被正确释放。(请参阅过渡到 ARC 发行说明的“使用生命周期限定符来避免强引用循环”部分。我知道您没有使用 ARC,但本节描述了 ARC 和手动引用计数解决方案。)


如果您不介意,我对您的代码还有其他意见:

  • 您不应该在completionBlock不将其分派到主队列的情况下从 更新 UI ...所有 UI 更新都应在主队列上发生:

    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];
        }];
    }];
    
  • 您在方法中使用了访问器方法init。作为一个良好的做法,你真的不应该。请参阅高级内存管理编程指南中的不要在初始化方法中使用访问器方法和 dealloc 。

  • 虽然我们可能已经解决了操作未释放的问题,但我怀疑除非您已经编写了UIScrollViewDelegate调用以释放已滚动出可见屏幕的图像的调用,否则您将继续遇到内存问题。话虽如此,您可能已经解决了这个问题,如果是这样,我什至为提及它而道歉。我只提出这个问题,因为它很容易解决这个NSOperation问题,但随后忽略了让滚动视图在它们滚动出屏幕时释放图像。

  • 我不确定您的子类NSOperation是否支持并发,因为您缺少在并发编程指南中定义自定义操作中讨论的一些关键方法。也许您已经这样做了,但为了简洁而省略了它。或者,我认为如果您使用为您处理这些内容的现有类之一(例如)会更容易。您的电话,但如果您追求并发,您需要确保将队列设置为合理的值,例如 4。NSOperationNSBlockOperationmaxConcurrentOperationCount

  • 您的代码有一些多余的retain语句。话虽如此,你也有必要的release陈述,所以你确保你不会有问题,但这只是有点好奇。显然,ARC 让你摆脱了这类东西的麻烦,但我很感激这是一大步。但是,当您有机会时,请查看 ARC,因为它可以让您不必担心很多此类问题。

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

于 2013-02-05T06:31:48.687 回答