4

The following code downloads 700+ images from a server with varying sizes, the issue here is that memory (even with ARC) is never released and eventually a memory warning appears followed by the application exiting. I've tried @autoreleasepool in this method and that didn't seem to work. Also I've tried stopping the for loop at different locations to see if memory is released after it finished, but it isn't.

This method is called inside a for loop and receives the image url and short name. It has been tried in a background thread and main thread with the same results (memory wise).

-(void)saveImage:(NSString*)image name:(NSString*)imageName{     
    int k = 0;
    for (int j = 0; j < [imageName length]; j++) {
        if ([imageName characterAtIndex:j] == '/') {
            k = j;
        }
    }if (k != 0) {
        imageName = [imageName substringFromIndex:k+1];
    }    

    NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@", imageName]]; 

    if ([fileManager fileExistsAtPath:fullPath]) {
        [fileManager removeItemAtPath:fullPath error:nil];
    }

    NSURL *url = [NSURL URLWithString:image];
    NSData *data = [[NSData alloc]initWithContentsOfURL:url];
    NSLog(@"Saved: %d:%@", [fileManager createFileAtPath:fullPath contents:data attributes:nil], url); 
    data = nil;
}  


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

int cacheSizeMemory = 4*1024*1024; // 4MB
int cacheSizeDisk = 32*1024*1024; // 32MB
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:@"nsurlcache"];
[NSURLCache setSharedURLCache:sharedCache];

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[[UIApplication sharedApplication] setStatusBarHidden:YES];
[self.window makeKeyAndVisible];
return YES;
}

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
NSLog(@"mem warning, clearing cache");
[[NSURLCache sharedURLCache] removeAllCachedResponses];
}

Allocations

ohr
  • 1,717
  • 14
  • 15
  • Have you run Instruments->Allocations and determined which objects are holding dirty memory in the VM Tracker? If so, can you confirm that it is the NSData objects? Perhaps post a screenshot with some details. – Robotic Cat Jul 05 '12 at 21:42
  • I have added a screenshot of Allocations tool, I've ran it a few times and it is always CFData at the top in `Overall Bytes`, yet `Live Bytes` always seem normal to me? Let me know if you need anything else, thanks for your help. – ohr Jul 06 '12 at 15:52
  • Hmm. Thought my post might have fixed your problem. Can you post an up-to-date screenshot (including the graph) of the Allocations and VM Tracker. Also, click on the VM Tracker (select Automatic Snapshotting) and check the *Dirty* objects. Make sure all the cache code is implemented. – Robotic Cat Jul 06 '12 at 17:47
  • 1
    There is a WWDC 2012 video I think you should review. It shows how to check that you are not leaking, abandoning or unnecessarily caching memory. The video is "Session 242 - iOS App Performance: Memory" and can be found on developer.apple.com. It will walk you through the steps you need to take to track the problem down. – Robotic Cat Jul 06 '12 at 17:59
  • Thanks again for your reply, I updated my OP. When I click VM Tracker though nothing happens and I can not see any of the options you suggested. I will look for that video, thanks! – ohr Jul 06 '12 at 18:07
  • OK - the new screenshot shows an improvement. Update your post when you've watched the video and followed their steps. Then we can have another look for potential solutions. – Robotic Cat Jul 06 '12 at 18:19

2 Answers2

5

Based on your screenshot, I think the issue is with the NSURL caching rather than the actual NSData objects. Can you try the following:

In your Application Delegate, setup an initial URL cache:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Do initial setup
    int cacheSizeMemory = 16*1024*1024; // 16MB
    int cacheSizeDisk = 32*1024*1024; // 32MB
    NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:@"nsurlcache"];
    [NSURLCache setSharedURLCache:sharedCache];

    // Finish the rest of your didFinishLaunchingWithOptions and head into the app proper
}

Add the following to your Application Delegate:

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
    [[NSURLCache sharedURLCache] removeAllCachedResponses];
}

A cache file will be created:"Library/Caches/your_app_id/nsurlcache"

The link to the Apple example is here: URL Cache

Code not tested but this (or something similar) should sort your problem + plus you can experiment with the cache sizes.

Can you post another screenshot of Allocations in action with this code? I would expect to see memory use stop growing and flatten out.

Robotic Cat
  • 5,899
  • 4
  • 41
  • 58
  • Thanks for your reply. Unfortunately memory still seems to be growing with each image downloaded and saved. I have added a second screenshot and it looks like you were right about the NSURL keeping the data. I have added your code to my `Application Delegate` and a `NSLog` inside memory warning method and I did not see it in the console. Thanks again for your help. – ohr Jul 06 '12 at 17:29
  • Looks like I found the memory warning sentence after all, and it does display my `NSLog` but it still crashes some time after that. – ohr Jul 06 '12 at 17:39
1

"dataWithContentsOfURL" returns an autoreleased NSData object and that doesn't normally get released until the end of the run loop or the end of the method, so it's no wonder you're filling up memory pretty quickly.

Change it to an explicit "initWithContentsOfURL" method and then force a release by doing "data = nil;" when you are absolutely done with the image data.

Community
  • 1
  • 1
Michael Dautermann
  • 88,797
  • 17
  • 166
  • 215
  • Thank you for the reply, I've changed the NSData to look as you suggested. NSData *data = [[NSData alloc] initWithContentsOfURL:url]; But the problem persists, I am using Activity Monitor to look at memory usage. *Forgot to mention I did add the data = nil sentence as well after. – ohr Jul 05 '12 at 19:29
  • the out of memory issue (and crash) still happens? or memory usage is still going up (which should still be happening, since there's still hundreds of autoreleased objects being created -- via those NSString methods -- over and over again)? – Michael Dautermann Jul 05 '12 at 19:33
  • Yes. The console still shows a "Received memory warning" sentence and a crash some time after. Memory usage goes up by around 1-2MB per second, which suggests the data (or something big) isn't being released. Thanks for your help. – ohr Jul 05 '12 at 19:38
  • 1
    Sorry for the late reply ohr but were you ever able to figure out what the issue was ? I have the exact same issue where the memory usage goes up 1-2 MB per second – user1861763 Jun 04 '13 at 15:09