Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preventing UIWebView to allocate memory indefinitely (NSURLCache apparently not working)

I have been trying to understand how the UIWebView cache works. Since my goal is to be able to manage the memory allocated by the UIWebView (at least, as much as possible), to avoid memory raising indefinitely and the App getting killed because of this.

After reading other stackoverflow questions and searching the web, I decided to try the NSURLCache sharedURLCache, but I cannot figure out how it really works.

My testing 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.html file, I have a JS script that downloads a JSON from Flickr with the last images uploaded to the their public feed. Each image is appended to a list. This whole process is repeated every second, until we reach 1000 images downloaded from the feed. See the code:

var nImg = 0;

function getData()
{
    $.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('<li id="' + image.title + '"><img src="' + image.media.m + '"/></li>');
        });
        $('#page > #imagesList').append(items.join''));
        nImg += items.length;
    });
    $('#page > #numImages').html(nImg);

    if(nImg < 1000)
        setTimeout("getData();", 1000); // Get more data in 1s
}

$(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 as I would expect if the UIWebView would be using this sharedURLCache to cache the images downloaded.

Instruments evolution after we reach 1000 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 really need to understand how this sharedURLCache works, and how the UIWebView uses it to cache URLRequest, images,... in case it is used at all. I do not want to have an App that uses a UIWebView that can allocate memory without control.

like image 557
veducm Avatar asked Mar 07 '13 15:03

veducm


1 Answers

How did you go with this in the end? I notice that your *sharedCache initWithMemoryCapacity has a diskPath argument that's nil.

From the apple doco:

In iOS, path is the name of a subdirectory of the application’s default cache directory in which to store the on-disk cache (the subdirectory is created if it does not exist).

Checkout this link by Two Bit Labs on how to use the cache: http://twobitlabs.com/2012/01/ios-ipad-iphone-nsurlcache-uiwebview-memory-utilization/

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.

like image 147
Ann Taylor Avatar answered Nov 03 '22 06:11

Ann Taylor