当我在滚动 UITableview 期间显示文档目录中的图像时,内存不断增加然后应用程序崩溃
Memory continuously increase then app crash when i display image from document directory during scrolling UITableview
我的要求是下载应用程序内存中的所有图像,并在可用时从本地显示。
下面是我从本地访问图像的代码,如果它不可用,那么它将下载然后显示。
[cell.imgProfilePic processImageDataWithURLString:cData.PICTURE];
我已经定制了UIImageView
class
DImageView.h
#import <UIKit/UIKit.h>
@interface DImageView : UIImageView
@property (nonatomic, strong) UIActivityIndicatorView *activityView;
- (void)processImageDataWithURLString:(NSString *)urlString;
+ (UIImage *)getSavedImage :(NSString *)fileName;
@end
DImageView.m
#import "DImageView.h"
#define IMAGES_FOLDER_NAME @"DImages"
@implementation DImageView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{ }
return self;
}
- (void)dealloc
{
self.activityView = nil;
[super dealloc];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self initWithFrame:[self frame]];
}
return self;
}
- (void)processImageDataWithURLString:(NSString *)urlString
{
@autoreleasepool
{
UIImage * saveImg =[DImageView getSavedImage:urlString];
if (saveImg)
{
@autoreleasepool
{
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_async(callerQueue, ^{
@autoreleasepool{
[self setImage:saveImg];
}
});
}
}
else
{
[self showActivityIndicator];
NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
__block NSError* error = nil;
dispatch_async(downloadQueue, ^{
@autoreleasepool
{
NSData * imageData = [NSData dataWithContentsOfURL:url options:NSDataReadingUncached error:&error];
if (!error)
{
dispatch_async(callerQueue, ^{
@autoreleasepool {
UIImage *image = [UIImage imageWithData:imageData];
[self setImage:image];
[self hideActivityIndicator];
[self saveImageWithFolderName:IMAGES_FOLDER_NAME AndFileName:urlString AndImage:imageData];
}
});
}
}
});
dispatch_release(downloadQueue);
}
}
}
- (void) showActivityIndicator
{
self.activityView = [[UIActivityIndicatorView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
self.activityView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
self.activityView.hidesWhenStopped = TRUE;
self.activityView.backgroundColor = [UIColor clearColor];
self.activityView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
[self addSubview:self.activityView];
[self.activityView startAnimating];
}
- (void) hideActivityIndicator
{
CAAnimation *animation = [NSClassFromString(@"CATransition") animation];
[animation setValue:@"kCATransitionFade" forKey:@"type"];
animation.duration = 0.4;;
[self.layer addAnimation:animation forKey:nil];
[self.activityView stopAnimating];
[self.activityView removeFromSuperview];
for (UIView * view in self.subviews)
{
if([view isKindOfClass:[UIActivityIndicatorView class]])
[view removeFromSuperview];
}
}
- (void)saveImageWithFolderName:(NSString *)folderName AndFileName:(NSString *)fileName AndImage:(NSData *) imageData
{
@autoreleasepool{
NSFileManager *fileManger = [[NSFileManager defaultManager] autorelease];
NSString *directoryPath = [[NSString stringWithFormat:@"%@/%@",[DImageView applicationDocumentsDirectory],folderName] autorelease];
if (![fileManger fileExistsAtPath:directoryPath])
{
NSError *error = nil;
[fileManger createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
}
fileName = [DImageView fileNameValidate:fileName];
NSString *filePath = [[NSString stringWithFormat:@"%@/%@",directoryPath,fileName] autorelease];
BOOL isSaved = [imageData writeToFile:filePath atomically:YES];
if (!isSaved)DLog(@" ** Img Not Saved");
}
}
+ (NSString *)applicationDocumentsDirectory
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
+ (UIImage *)getSavedImage :(NSString *)fileName
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
fileName = [DImageView fileNameValidate:fileName];
NSFileManager * fileManger = [[NSFileManager defaultManager] autorelease];
NSString * directoryPath = [[NSString stringWithFormat:@"%@/%@",[DImageView applicationDocumentsDirectory],IMAGES_FOLDER_NAME] autorelease];
NSString * filePath = [[NSString stringWithFormat:@"%@/%@",directoryPath,fileName] autorelease];
if ([fileManger fileExistsAtPath:directoryPath])
{
UIImage *image = [[[UIImage imageWithContentsOfFile:filePath] retain]autorelease];
if (image)
return image;
else
return nil;
}
[pool release];
return nil;
}
+ (NSString*) fileNameValidate : (NSString*) name
{
name = [name stringByReplacingOccurrencesOfString:@"://" withString:@"##"];
name = [name stringByReplacingOccurrencesOfString:@"/" withString:@"#"];
name = [name stringByReplacingOccurrencesOfString:@"%20" withString:@""];
return name;
}
@end
一切正常,滚动流畅,后台异步下载图像。
问题是当我滚动时 UITableview
应用程序内存不断增加,一段时间后我得到 Receive memory waring
2/3 次然后应用程序崩溃。
当我使用 AsyncImageView
class 时内存不会增加并且工作正常。但由于应用程序要求,我将所有图像保存到 Document Directory
并在可用时从中显示。
我已经尝试使用 @autoreleasepool
和 release
一些变量但没有成功。
如果有人有管理内存管理的解决方案,我将不胜感激。
**ARC is off in my application.**
UIImagePNGRepresentation returns 可能是非自动释放的对象 - 您可以尝试释放它,看看是否会导致崩溃。显然你没有发布任何东西,但除了图像表示之外没有什么是显而易见的。
其他一些评论:
运行 您在 Instruments 中的应用程序,使用 ObjectAlloc 工具,应该立即清楚哪些对象没有被释放。如果您不了解 Instruments,那么,现在是时候学习它了。
您可以 'track' 对象并在使用 ObjectTracker 释放它们时收到一条消息 - 但是它是为 ARC 设计的,因此您可能需要对其进行调整。如果你使用它,当你的每个对象被释放时你会看到一条消息
当 table 视图完成一个单元格时,您可以收到一个委托方法告诉您,然后您可以 nil(释放)并反对该单元格保留
您对 downloadQueue
的使用很奇怪 - 在您的实例中将其作为 ivar 创建一次,根据需要使用它,然后在 dealloc 中释放它
您在主队列中隐藏了 activity 微调器,但不要在主队列中启动它
您命令 activity 视图将其自身从其父视图中删除,但随后在子视图中查找并尝试将其删除:
[self.activityView removeFromSuperview];
for (UIView * view in self.subviews)
{
if([view isKindOfClass:[UIActivityIndicatorView class]])
[view removeFromSuperview];
}
最后,Instruments 就是你想要的。您可以在这里阅读更多相关信息,或者只是 google,您一定会找到大量的博客来阅读。
根据我的理解,您的 "getSavedImage" 方法存在问题,您必须手动管理内存而不是 'autorelease',因此我的建议是使用
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath]
and also release it after use of it. means after '[self setImage:saveImg];'
[saveImg release]
而不是这个。
[[UIImage imageWithContentsOfFile:filePath] retain];
“不要使用 Autorelease,因为它一直保留在内存中,直到池没有耗尽”,正因为如此,您遇到了内存问题。
是的,我终于解决了。
问题中的代码现在工作正常。但是如果没有 release
一些对象和代码中的 @autoreleasepool
块,内存在滚动期间不断增加 UITableView
。
从 Instrument
我发现 UILableView
和 UIImageView
的内存增加了。我正在使用 Custom UITableViewCell
并且在那个文件中我没有实现 dealloc
方法。所以当我在 UITableViewCell
.m
文件和 release
& nil
所有对象中实现 dealloc
方法时。
在那之后 memory
在滚动期间不会增加 TableView
并且它解决了问题。
我的要求是下载应用程序内存中的所有图像,并在可用时从本地显示。
下面是我从本地访问图像的代码,如果它不可用,那么它将下载然后显示。
[cell.imgProfilePic processImageDataWithURLString:cData.PICTURE];
我已经定制了UIImageView
class
DImageView.h
#import <UIKit/UIKit.h>
@interface DImageView : UIImageView
@property (nonatomic, strong) UIActivityIndicatorView *activityView;
- (void)processImageDataWithURLString:(NSString *)urlString;
+ (UIImage *)getSavedImage :(NSString *)fileName;
@end
DImageView.m
#import "DImageView.h"
#define IMAGES_FOLDER_NAME @"DImages"
@implementation DImageView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{ }
return self;
}
- (void)dealloc
{
self.activityView = nil;
[super dealloc];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self initWithFrame:[self frame]];
}
return self;
}
- (void)processImageDataWithURLString:(NSString *)urlString
{
@autoreleasepool
{
UIImage * saveImg =[DImageView getSavedImage:urlString];
if (saveImg)
{
@autoreleasepool
{
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_async(callerQueue, ^{
@autoreleasepool{
[self setImage:saveImg];
}
});
}
}
else
{
[self showActivityIndicator];
NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
__block NSError* error = nil;
dispatch_async(downloadQueue, ^{
@autoreleasepool
{
NSData * imageData = [NSData dataWithContentsOfURL:url options:NSDataReadingUncached error:&error];
if (!error)
{
dispatch_async(callerQueue, ^{
@autoreleasepool {
UIImage *image = [UIImage imageWithData:imageData];
[self setImage:image];
[self hideActivityIndicator];
[self saveImageWithFolderName:IMAGES_FOLDER_NAME AndFileName:urlString AndImage:imageData];
}
});
}
}
});
dispatch_release(downloadQueue);
}
}
}
- (void) showActivityIndicator
{
self.activityView = [[UIActivityIndicatorView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
self.activityView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
self.activityView.hidesWhenStopped = TRUE;
self.activityView.backgroundColor = [UIColor clearColor];
self.activityView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
[self addSubview:self.activityView];
[self.activityView startAnimating];
}
- (void) hideActivityIndicator
{
CAAnimation *animation = [NSClassFromString(@"CATransition") animation];
[animation setValue:@"kCATransitionFade" forKey:@"type"];
animation.duration = 0.4;;
[self.layer addAnimation:animation forKey:nil];
[self.activityView stopAnimating];
[self.activityView removeFromSuperview];
for (UIView * view in self.subviews)
{
if([view isKindOfClass:[UIActivityIndicatorView class]])
[view removeFromSuperview];
}
}
- (void)saveImageWithFolderName:(NSString *)folderName AndFileName:(NSString *)fileName AndImage:(NSData *) imageData
{
@autoreleasepool{
NSFileManager *fileManger = [[NSFileManager defaultManager] autorelease];
NSString *directoryPath = [[NSString stringWithFormat:@"%@/%@",[DImageView applicationDocumentsDirectory],folderName] autorelease];
if (![fileManger fileExistsAtPath:directoryPath])
{
NSError *error = nil;
[fileManger createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
}
fileName = [DImageView fileNameValidate:fileName];
NSString *filePath = [[NSString stringWithFormat:@"%@/%@",directoryPath,fileName] autorelease];
BOOL isSaved = [imageData writeToFile:filePath atomically:YES];
if (!isSaved)DLog(@" ** Img Not Saved");
}
}
+ (NSString *)applicationDocumentsDirectory
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
+ (UIImage *)getSavedImage :(NSString *)fileName
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
fileName = [DImageView fileNameValidate:fileName];
NSFileManager * fileManger = [[NSFileManager defaultManager] autorelease];
NSString * directoryPath = [[NSString stringWithFormat:@"%@/%@",[DImageView applicationDocumentsDirectory],IMAGES_FOLDER_NAME] autorelease];
NSString * filePath = [[NSString stringWithFormat:@"%@/%@",directoryPath,fileName] autorelease];
if ([fileManger fileExistsAtPath:directoryPath])
{
UIImage *image = [[[UIImage imageWithContentsOfFile:filePath] retain]autorelease];
if (image)
return image;
else
return nil;
}
[pool release];
return nil;
}
+ (NSString*) fileNameValidate : (NSString*) name
{
name = [name stringByReplacingOccurrencesOfString:@"://" withString:@"##"];
name = [name stringByReplacingOccurrencesOfString:@"/" withString:@"#"];
name = [name stringByReplacingOccurrencesOfString:@"%20" withString:@""];
return name;
}
@end
一切正常,滚动流畅,后台异步下载图像。
问题是当我滚动时 UITableview
应用程序内存不断增加,一段时间后我得到 Receive memory waring
2/3 次然后应用程序崩溃。
当我使用 AsyncImageView
class 时内存不会增加并且工作正常。但由于应用程序要求,我将所有图像保存到 Document Directory
并在可用时从中显示。
我已经尝试使用 @autoreleasepool
和 release
一些变量但没有成功。
如果有人有管理内存管理的解决方案,我将不胜感激。
**ARC is off in my application.**
UIImagePNGRepresentation returns 可能是非自动释放的对象 - 您可以尝试释放它,看看是否会导致崩溃。显然你没有发布任何东西,但除了图像表示之外没有什么是显而易见的。
其他一些评论:
运行 您在 Instruments 中的应用程序,使用 ObjectAlloc 工具,应该立即清楚哪些对象没有被释放。如果您不了解 Instruments,那么,现在是时候学习它了。
您可以 'track' 对象并在使用 ObjectTracker 释放它们时收到一条消息 - 但是它是为 ARC 设计的,因此您可能需要对其进行调整。如果你使用它,当你的每个对象被释放时你会看到一条消息
当 table 视图完成一个单元格时,您可以收到一个委托方法告诉您,然后您可以 nil(释放)并反对该单元格保留
您对
downloadQueue
的使用很奇怪 - 在您的实例中将其作为 ivar 创建一次,根据需要使用它,然后在 dealloc 中释放它您在主队列中隐藏了 activity 微调器,但不要在主队列中启动它
您命令 activity 视图将其自身从其父视图中删除,但随后在子视图中查找并尝试将其删除:
[self.activityView removeFromSuperview];
for (UIView * view in self.subviews)
{
if([view isKindOfClass:[UIActivityIndicatorView class]])
[view removeFromSuperview];
}
最后,Instruments 就是你想要的。您可以在这里阅读更多相关信息,或者只是 google,您一定会找到大量的博客来阅读。
根据我的理解,您的 "getSavedImage" 方法存在问题,您必须手动管理内存而不是 'autorelease',因此我的建议是使用
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath]
and also release it after use of it. means after '[self setImage:saveImg];'
[saveImg release]
而不是这个。
[[UIImage imageWithContentsOfFile:filePath] retain];
“不要使用 Autorelease,因为它一直保留在内存中,直到池没有耗尽”,正因为如此,您遇到了内存问题。
是的,我终于解决了。
问题中的代码现在工作正常。但是如果没有 release
一些对象和代码中的 @autoreleasepool
块,内存在滚动期间不断增加 UITableView
。
从 Instrument
我发现 UILableView
和 UIImageView
的内存增加了。我正在使用 Custom UITableViewCell
并且在那个文件中我没有实现 dealloc
方法。所以当我在 UITableViewCell
.m
文件和 release
& nil
所有对象中实现 dealloc
方法时。
在那之后 memory
在滚动期间不会增加 TableView
并且它解决了问题。