2013-02-04 66 views
2

我遇到了一个问题,那就是从内存(1MB〜5MB)中看不到相对较大的图像。当用户滚动浏览一组图像时,以下代码块被调用。大约15张图片后,应用程序将崩溃。有时候,“didReceiveMemoryWarning”被调用,有时它不会 - 应用程序只会崩溃,停止,退出调试,而不会在任何代码行中停止 - 什么都不会。我认为这是设备内存不足时发生的情况?另一个担心是'dealloc'似乎永远不会被调用到子类“DownloadImageOperation”中。有任何想法吗?在NSOperation中设置UIImage时发生内存泄漏

获取和设置图像:

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

您能否在您的项目中使用ARC?它通常简化了内存管理。 – xyzzycoder

+0

不幸的是,这不是使用ARC。这个项目在iOS 5之前开始。我很想找到一些方法来转换它。 – Erracity

+1

@Erracity - 我认为转向ARC确实值得,即使第一次这样做需要一点努力。它可以让你摆脱内存管理的杂草,并消除了很多简单的泄漏。你仍然需要使用'block_mageOp'技巧的'__weak'变体,以避免完成块中的保留周期,但是所有的手动调用''__weak DownloadImageOperation * weakImageOp = imageOp;''保留'和'释放'走开。 – Rob

回答

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限定符添加到操作和使用,而不是imageOpcompletionBlock内,避免了有力的参考周期。在手动引用计数,可避免通过使用保留周期__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]; 
}]; 

我想,如果你这样做,你会看到你的操作得到正确发布。 (见“使用生命周期预选赛避免强烈参考周期”部分的Transitioning to ARC Release Notes我你不使用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方法中使用存取器方法。作为一个良好的做法,你真的不应该这样做。请参阅高级内存管理编程指南中的Don’t Use Accessor Methods in Initializer Methods and dealloc

  • 虽然我们可能有固定的工作没有得到释放的问题,我会怀疑,除非你已经编码的UIScrollViewDelegate呼吁释放已滚动到屏幕可视图像,你会继续有内存问题。话虽如此,你可能已经解决了这个问题,如果是这样,我甚至提起它,我很抱歉。我只提出这个问题,因为它可以很容易地解决这个问题,但是忽略了在滚动屏幕时滚动视图释放图像。

  • 我不知道你的子类NSOperation将支持并发为你缺少一些并发编程指南在Defining a Custom Operation讨论的关键方法。也许你已经这样做了,但为了简洁起见省略了它。或者,我认为如果您使用现有的NSOperation课程(例如NSBlockOperation)之一来帮助您处理这些问题,则会更容易。你的电话,但如果你追求并发性,你会想确保你设置队列的maxConcurrentOperationCount为合理的东西,如4.

  • 你的代码有一些冗余的retain语句。话虽如此,你也有必要的release陈述,所以你确保你不会有问题,但它只是有点好奇。很明显,ARC可以帮助你摆脱杂草,但是我很欣赏这是一个很大的进步。但是,当你有机会时,看看ARC,因为它可以让你不必担心很多问题。

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

+1

我应该在OP中提到,这不是使用ARC,但我在其他项目。这个项目在iOS 5之前就开始了。然而,知道这会导致一个保留周期,这是一个非常有用的例子。我运行分析并清除了很多明显的缺陷,但分析没有在上述代码中找到任何内容。非常感谢Rob,对上述代码进行了非常全面的批评。 – Erracity

+0

不应该行'__block DownloadImageOperation * blockImageOp = imageOp;'实际上'__weak DownloadImageOperation * blockImageOp = imageOp;'? – SpaceDog

+0

不是这种情况。如果他使用ARC,是的,但这是一个手动引用计数问题。手动引用计数代码中不能使用弱。 – Rob