[英]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.