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

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.
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.

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.

Related

Is it possible to clear only the in memory cache in a NSURLCache?

This post recommends doing this:
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
[[NSURLCache sharedURLCache] removeAllCachedResponses];
}
However, that will take both the on disk and the in memory cache. Does something like this work to just evict the in memory cache?
NSURLCache *cache = [NSURLCache sharedURLCache];
NSUInteger currentMemoryCapacity = cache.memoryCapacity;
// Not sure if this works? Docs seem to say it will trigger an evict.
// https://developer.apple.com/library/mac/documentation/cocoa/reference/foundation/Classes/NSURLCache_Class/Reference/Reference.html#//apple_ref/occ/instm/NSURLCache/setMemoryCapacity:
cache.memoryCapacity = 0;
cache.memoryCapacity = currentMemoryCapacity;
Yes, that code should evict the memory cache. (And does on iOS 7.1.1, in my brief test.)
If you're looking for finer control over the cache, check out Peter Steinberger's fork of SDURLCache which has (for example) a -removeAllCachedResponsesInMemory method.
This no longer appears to work (tested on the iOS 9.2 simulator)
Although the cache's reported memoryCapacity changes, the actual memoryUsage reported doesn't :-(

How to cache file downloads with AFNetworking?

I am trying to download some files.
For each cell, I check if the file exists and is already stored in a directory, if not I download it.
But I also download files for the next couple of cells.
What happens, if I am trying to download a file for an invisible cell ahead of time, but then the user scrolls to the cell and the file is not fully downloaded yet.
Is there anyway to prevent it from downloading twice?
Is there anyway to know that the file is already getting downloaded?
I am using AFNetworking.
What you're trying to do has a name and it's caching.
AFNetworking already implements it as per the official FAQs
Does AFNetworking have any caching mechanisms built-in?
AFNetworking takes advantage of the caching functionality already provided by NSURLCache and any of its subclasses. So long as your NSURLRequest objects have the correct cache policy, and your server response contains a valid Cache-Control header, responses will be automatically cached for subsequent requests.
Set up the cache as follows and you'll be ok
- (void)setupCache {
NSURLCache *urlCache = [[NSURLCache alloc] initWithMemoryCapacity:1024*1024*4 // 1MB mem cache
diskCapacity:1024*1024*5 // 5MB disk cache
diskPath:nil];
[NSURLCache setSharedURLCache:urlCache];
}
Then just perform requests normally and if the resource is in cache you'll get a cache hit and you won't be downloading it twice.
Note that if you have to support anything below iOS5, you have to use an alternative URL cache like SDURLCache and set it up like follows
- (void)setupCache {
SDURLCache *urlCache = [[SDURLCache alloc] initWithMemoryCapacity:1024*1024 // 1MB mem cache
diskCapacity:1024*1024*5 // 5MB disk cache
diskPath:[SDURLCache defaultCachePath]];
[NSURLCache setSharedURLCache:urlCache];
}
The reason is well explained in this article, but it can be summarized by the following quote:
Before iOS5, NSURLCache just saved requests to memory, even if the documentation said otherwise - the diskCapacity property was silently ignored

UIWebview caching to disk

I have a simple iOS app that loads and list of links and presents them in a UIWebview. The app, when installed, is ~2MB. After clicking around through some of the links, I noticed that the disk usage of documents and data jumped to over 10MB. As a couple days went on, I noticed the disk usage continued to grow. My question is, what is the best way to manage disk storage on iOS?
The goal of this app is to be very minimal and not heavy on resources, but I'm concerned that it will continue to consume more and more disk space with, what I assume is, cached web content. Should I / is there a way to set a cache size? Or does iOS just handle this for me and delete old data eventually?
Do not optimize prematurely! Resources on disk put no strain on RAM memory, so don't worry about it. If the system is managing caching for you, it is not up to you to guess what it will do. If it needs to clear space, it will presumably clear space. Don't worry, be happy.
Now, having said, you do get some control over a NSURLRequest caching policy. Look at the NSURLRequest docs to learn more about that.
Specify cache size:
int cacheSizeMemory = 8 * 1024 * 1024; // 8MB
int cacheSizeDisk = 32 * 1024 * 1024; // 32MB
NSURLCache* sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:#"nsurlcache"];
[NSURLCache setSharedURLCache:sharedCache];
Remove all stored cached URL responses:
[[NSURLCache sharedURLCache] removeAllCachedResponses];
Hope to be useful to you!

NSURLCache Images Using AFNetworking API

I have an app which is downloading images from the server. The images are approx 56 KB and there are at least 30 of them. There are times when the user either have no connection or Edge connection (Dialup).
I am using NSURLCache in my AppDelegate.h like this:
- (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];
But for some reason it does not cache the images. If I load all the images with internet connection available and then disconnect or use Edge network it again fetches the images.
Edit: iOS 5 and onwards supports disk caching if you use NSURLRequest's NSURLRequestReturnCacheDataElseLoad cachePolicy or return the appropriate Cache-Control headers.
This issue about caching is explained in the AFNetworking F.A.Q..
In summary, it seems that on iOS, NSURLCache does not provide disk cache capability, even if you create an NSURLCache with a diskCapacity>0 (it seems to be ignored), and the solution is to use some other implementation of NSURLCache like the SDURLCache suggested in the FAQ.
You need to have access to the server to ensure that it is sending back the cache-control header for each request for the NSURLCache to be of any use. If you don't or can't get that setup (like in my scenario) you'll need to roll your own caching layer or use something like SDURLCache or RNCachingURLProtocol.

How to set cache size in UIwebView?

I have an app of UIWebview, I open the webpage A, there are lots of images in webpage A.
so it was very slow when i was first time to open it.
and i opened another webpage B and go back to webpage A, it was very fast and show images directly.
then I opened lots of other webpage and go back to webpage A,
the webpage A was slow again, the webpage A did not change anyghing.
it looks like the uiwebivew cache size is not enough when i opened lots of other webpage,so it delete the old record.
how should i avoid this issue?
Thanks in advance!
See the NSURLCache documentation.
You could be able to set the cache value as follows:
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:1000000 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];
sharedCache = nil;

Resources