Search code examples
objective-ciosnsurlconnectionnsurlcache

How to handle a few thousand NSURLConnection requests, download images and keep the memory usage down


I'm working on an app that downloads resources and writes them to disk for later offline use and it's always custom content. Currently we're working with content where there are about 4000 JPGs. The user initializes the download of the content onto the iPad and there's a progress bar in the UI, so the user does basically wait until it's done. Problem is that around 180 - 190 MB of memory allocated, it crashes.

What I've seen in Instruments is that CFData (store) is the main culprit and my understanding is that CFData (store) is the cache for NSURLConnection requests.

I've tried:

NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];

and

[[NSURLCache sharedURLCache] removeAllCachedResponses];

as well as setting the Cache policy, to no improvement.

For reference, this is what my post request looks like:

NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData];
[request setURL:[NSURL URLWithString:urlString]];
[request setHTTPMethod:@"POST"];

NSString *contentType = [NSString stringWithFormat:@"text/xml"];
[request addValue:contentType forHTTPHeaderField: @"Content-Type"]; 

NSMutableData *postBody = [NSMutableData data];
[postBody appendData:[xmlMessage dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPBody:postBody];

//get response
NSHTTPURLResponse* urlResponse = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&httpError];

Any help would be met with great applause.


Solution

  • If you really want to do this with synchronous requests, then you might consider explicitly retianing and releasing the objects used in each request. Using the autorelease pool means that the leftover detritus from every request sits in the autorelease pool until the pool is drained.

    If you have a for loop or some other loop handling these requests, you can also create a more local autorelease pool within the loop and then drain it just before the loop ends. If you do that and you have data that you want to retain beyond the scope of that loop (and the pool) you should retain it in the loop and relase it at some point later.

    At some point, depending on your experience level, you should consider doing this all on a secondary thread. You can treat each fetch as a self-contained operation and then used something like NSOperation and NSOperationQueue to manage it. That way, you can launch multiple operations simulataneously and not block your thread while waiting for each response.