簡體   English   中英

在NSOperation中設置UIImage時內存泄漏

[英]Memory leak when setting a UIImage in an NSOperation

我遇到的問題是,相對較大的圖像似乎永遠不會從內存中釋放出來(大小為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;
}

為這個巨大的代碼塊道歉。 我真的不知道問題可能在哪里,所以我試圖盡可能包容。 謝謝閱讀。

未被釋放的操作中的主要問題是您有一個保留周期,這是由完成塊中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限定符,並在completionBlock使用而不是imageOp ,以避免強引用循環。 在手動引用計數中,可以通過使用__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是否支持並發性,因為您缺少在“ 並發編程指南”定義自定義操作中討論的一些關鍵方法 也許你已經這樣做了,但為了簡潔起見省略了它。 或者,我認為如果你使用現有的NSOperation類(例如NSBlockOperation )來處理這些東西會更容易。 你的電話,但如果你追求並發,你需要確保你將隊列的maxConcurrentOperationCount設置為合理的,比如4。

  • 您的代碼有一些冗余的retain語句。 話雖如此,你也有必要的release聲明,所以你已經確保你不會遇到問題,但這只是有點好奇。 顯然,ARC讓你擺脫了那種雜草,但我很欣賞這是一個很大的進步。 但是當你有機會的時候,看看ARC,因為它可以讓你不必擔心這個問題。

  • 您可能應該通過靜態分析器運行代碼(“產品”菜單上的“分析”),因為您有一些死庫等。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM