简体   繁体   中英

Memory continuously increase then app crash when i display image from document directory during scrolling UITableview

My Requirement is download all images in application memory and display it from local if its available.

Below is my code to access image from local and if its not available then it will download then display.

[cell.imgProfilePic processImageDataWithURLString:cData.PICTURE];

I have made custom 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

Everything is working fine with smooth scrolling as well as asyncImage download in background.

The issue is when i scroll UITableview application memory is continuously increase and after some time i got Receive memory waring 2/3 time then application crash.

When i use AsyncImageView class that time memory not increase and its working fine. But due to app requirement i saved all images to Document Directory and display from it if its available.

i have tried with @autoreleasepool and release some variable but not getting success.

I appreciated if any one have the solution to manage memory management.

 **ARC is off in my application.** 

It's possible that UIImagePNGRepresentation returns non-autoreleased object - you can try to release it and see if that results in a crash. Obviously you are not releasing something, but nothing other than the image representation appears obvious.

A few other comments:

  • run your app in Instruments, using the ObjectAlloc tool, and it should be immediately obvious what objects are not dealloced. If you don't know Instruments, well, its time now to learn it.

  • you can 'track' objects and get a message when they are dealloced using ObjectTracker - however it was designed for ARC so you may need to tweak it. If you use it you would see a message when each of your objects are dealloced

  • when the table view is done with a cell, there is a delegate method that you can receive that tells you so, and you can then nil (release) and objects the cell retains

  • your use of downloadQueue is bizarre - create it once in your instance as an ivar, use it as you need, and in dealloc release it

  • you hide the activity spinner on the main queue, but don't start it on the main queue

  • you command the activity view to remove itself from its superview, but then look for in in the subviews and try to remove it there:

[self.activityView removeFromSuperview];

for (UIView * view in self.subviews)
{
    if([view isKindOfClass:[UIActivityIndicatorView class]])
        [view removeFromSuperview];
}

In the end, Instruments is what you want. You can read up more about it here, or just google and you will surely find a slew of blogs to read.

Yes Finally i have resolved it.

The code which is in Question is working fine now. but Without release some objects and @autoreleasepool block which is in code, memory was increase continuously during scroll UITableView .

From the Instrument i found that memory increase in UILableView and UIImageView . I am using Custom UITableViewCell and in that file i havnt implement dealloc method. So When i have implement dealloc method in UITableViewCell .m file and release & nil all object.

After that memory not increase during scroll TableView and its Resolved the issue.

As per my Understanding there is an issue in your "getSavedImage" Method you have to manage memory Manually instead of 'autorelease' so as My suggestion is use

UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath]
and also release it after use of it. means after '[self setImage:saveImg];'
[saveImg release]

instead of this.

[[UIImage imageWithContentsOfFile:filePath] retain];

'Don't Use Autorelease because it has staying in memory until pool not drain' and just because of this you got an memory issue.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM