Two Bit Labs Two Bit Labs
Search
Reduce iOS memory utilization by taming NSURLCache

If your iPhone or iPad app embeds UIWebViews or makes HTTP requests directly with NSURLConnection, it’s important to keep an eye on memory utilization by running it through the profiler occasionally. Web requests can use a lot of memory, and you may find the memory footprint grows and grows the more you use the app. One of the leading causes we see of high memory utilization in an app is failing to explicitly configure the NSURLCache.

Configuring the shared cache

First off, let’s configure the cache when the app starts (before any requests are made) so we can control the amount of memory it utilizes. In your UIApplicationDelegate:

- (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"] autorelease];
    [NSURLCache setSharedURLCache:sharedCache];
    ...

The above configures a 4MB in-memory cache with a 32MB disk cache. The disk cache will reside in the default iOS cache directory in a sub-directory called “nsurlcache”.

Responding to memory warnings

The most frequent cause of crashes we’ve seen in apps that use web views is being ejected for not freeing up enough memory when a memory warning comes in. When your app receives a memory warning, you should purge the shared cache to free up memory. Do this in your UIApplicationDelegate:

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

Now when you run your app in the profiler, you should see the memory utilization flatten out, and if you trigger a memory warning in the simulator you should see the memory usage drop.

A word of warning

There are a lot of recommendations on StackOverflow about purging the NSURLCache by recreating it, however, we’ve seen this lead to occasional crashes when requests occur on another thread while the cache is being recreated. Our advice is therefore to create the cache once when the app starts and purge it when appropriate.

arrow12 Responses

  1. Kevin
    15 mos, 3 wks ago

    Good article.

    What’s default behavior of system NSURLCache policy? And what result will be expected if we use system behavior and do nothing?

    How to calculate all memory size taken up by NSURLCache during app running?

  2. Steve Johnson
    14 mos ago
  3. 13 mos, 4 wks ago

    Steve, starting with iOS 5.x when I declare a disk cache it does indeed create a cache file in Library/Caches/my.app.id/nsurlcache. Whereas in 4.x it does not create a cache file.

    The good news is that either way there’s no downside to declaring a cache file!

  4. 13 mos, 4 wks ago

    Kevin, if you use the system behavior the cache data objects will grow very large and will not shrink on memory warnings. I’m not sure what the upper limit is but if you run your app through the memory profiler and keep making requests you’ll see it memory utilization grows and grows and grows taking up more and more memory. When you set the limits explicitly you’ll see your memory utilization flatten out when the cache is filled.

    Relying on the default system behavior will result in your app being ejected by the OS more often than it should in low memory situations which is why we recommend always setting the cache limits explicitly and clearing the cache on memory warnings.

  5. Steve Johnson
    13 mos, 4 wks ago

    Ah, I’ve been testing with the 4.3 simulator for backwards compatibility. Thanks for the heads up, I’ll try it on 5.x

  6. Kevin
    13 mos, 2 wks ago

    Thanks you guy.

    Plus one more question, my app is based on HTML 5, support offline feature by manifest, so you will always see file ApplicationCache.db in your app directory, does it also obey with the rule of NSURLCache? Is it the same on general principle? Thanks.

  7. 13 mos, 2 wks ago

    Kevin, HTML 5 offline storage is completely independent of the shared NSURLCache so your cache settings will have no effect on your HTML 5 data store.

  8. 10 mos, 4 wks ago

    Well, i tryed to use NSURLCache those days, because i would like to cache a JSON response which is 478.52KB GZipped and 2.88MB openend.
    I tryed to set-up my own sharedcache by 4MB in memory (also tryed 10MB) and 20MB on disk, but the cache never seems to trigger. Only requests which are 500KB or lower.

    I tryed this on my iPhone 3GS (where 128MB is the maximum empty RAM i can obtain, over 256MB) with iOS 5.1.1.

    I’ll try to make a test with memory profile to see what’s happening exactly, but if it’s the case, this is pretty useless. In fact I’m actually MKNetworKit which makes a dump of memory when it triggers (and cache work pretty well).

    Do you have any idea on what’s happening? On Stackoverflow no one seems having this problem (apart this one http://stackoverflow.com/questions/7166422/nsurlconnection-on-ios-doesnt-try-to-cache-objects-larger-than-50kb).

    Thanks,
    Julian

  9. 10 mos, 4 wks ago

    Julian, it’s certainly possible that there’s some such limitation in the default NSURLCache implementation. You could try rolling your own cache subclass or using one of the existing ones mentioned in the replies to that SO question.

    You might also check the cache-control headers on your HTTP response. It’s possible your server is telling the client not to cache.

  10. Todd Mathison
    7 mos, 4 wks ago

    Where are HTML 5 resources cached and how do you clear it? I deleted both the Cache.db and the ApplicationCache.db files and my page was still cached.

    Thanks!

    Todd

  11. Edu
    2 mos, 2 wks ago

    Thanks for this amazing post. It is very clear. Although, I have been trying to understand how this works with a UIWebView and I cannot really understand how the UIWebView is using this NSURLCache sharedURLCache.

    My scenario is this one:
    I have implemented a test app for iOS 5 where I have a single view with a UIWebView inside. This UiWebview is going to load a local index.html file like this:

    // Create an NSString from the local HTML file
    NSString *fullURL = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html" inDirectory:@"www"];
    NSURL *url = [NSURL fileURLWithPath:fullURL];
    NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
    // Load local HTML file
    [self.webView loadRequest:requestObj];

    In this index file, I have a JS script that downloads a JSON from Flickr with the last public images uploaded to the public feed. Each image is appended as an inside a list. This process is repeated every 10 seconds, until we reach 1000 images downloaded from the feed. See the code:

    var nImg = 0;

    function getData()
    {
        console.log('Adding more pics!!');
        $.getJSON('http://api.flickr.com/services/feeds/photos_public.gne?format=json&jsoncallback=?', function(data)
        {
            var items = [];
            $.each(data.items, function(i, image) {
                items.push('');
            });
            $('#page' + activeView + '> #imagesList').append(items.join(''));
            nImg += items.length;
        });
        $('#page' + activeView + ' > #numImages').html(nImg);
                   
        if(nImg < 1000)
            setTimeout("getData();", 10000); // Get more data in 10s
    }
           
    $(document).ready(function()
    {
        getData();
    });

    Finally, in my AppDelegate, I set up the sharedURLCache as written in this post:

    // Initialize the Cache, so we can control the amount of memory it utilizes
    int cacheSizeMemory = 4*1024*1024; // 4MB
    int cacheSizeDisk = 32*1024*1024; // 32MB
    NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:nil];

    [NSURLCache setSharedURLCache:sharedCache];

    But, despite what it is supposed to happen, when I open instruments and run the app checking the allocations, the memory keeps growing while we download images (up to 20MB), instead of flattening around 4-6MB, that is what I would expect if the UIWebView would be using this sharedURLCache to cache the images.

    Does anybody know what am I doing wrong? Do I have misunderstood something? Am I loading the page wrong? Does the UIWebView uses another cache for the images loaded in the JS?

    Please, let me know your thoughts. I can share the whole project if necessary, because I really want to understand how this sharedURLCache works, and how the UIWebView uses it, in case it is used at all.

    Thank you very much.
    Edu

  12. aki
    1 mo, 1 wk ago

    @Edu , is the issue what you have posted in this forum got solved ? If so what was the solution ? Would you please reply because I am too facing the same kind of issue. Thanks.

Leave A Comment