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.
Related
I have an app that uses UIWebViews. I am using RNCachingURLProtocol to cache pages for offline browsing. RNCachingProtocol uses NSURLConnection in requests. In my AppDelegate I have the following code:
NSUInteger cacheSizeInMemory = 100 * 1024 * 1024;
NSUInteger cacheSizeOnDisc = 100 * 1024 * 1024;
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeInMemory diskCapacity:cacheSizeOnDisc diskPath:#"nsurlcache"];
[NSURLCache setSharedURLCache:sharedCache];
I have tested the RNCachingProtocol and confirmed that it does cache web content to disc. Since RNCachingURLProtocol uses NSURLConnection, am I to assume that my UIWebView objects will make use of the NSURLCache defined in my AppDelegate? Is there any clear way to test this? Thanks!
UIWebView use shared url cache so yes, [NSURLCache setSharedURLCache:sharedCache]; sets cache that will be used by UIWebView.
The easiest to see this would be subclassing NSURLCache and do something in
-(NSCachedURLResponse*)cachedResponseForRequest:(NSURLRequest*)request;
method. Then set your subclass as shared cache and see for yourself.
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 :-(
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
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!
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.