How to cache file downloads with AFNetworking? - ios

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

Related

Empty the cache programmatically in iOS

Does anyone by coincidence know how I can empty the cache memory of the iOS app that I am developing, in the moment it goes to the background (applicationDidEnterBackground)? I have investigated about NSCache but I am still not able to understand how could I retrieve the cache to basically remove/free it?
Is this what you're talking about?
[[NSURLCache sharedURLCache] removeAllCachedResponses];
You can also modify the cache behavior of your requests to selectively cache responses. If you're using AFNetworking by chance, you can use setCacheResponseBlock. E.g. in one project I set it to return nil for all large video and audio files. But allow it to cache smaller image files.
[streamingOperation setCacheResponseBlock:^NSCachedURLResponse *(NSURLConnection *connection, NSCachedURLResponse *cachedResponse) {
return nil; // Ensures we are not unecessarily caching asset data to Cache.db
}];

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!

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

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.

NSURLCache and .svg files

I am caching locally some of the larger files required for a UIWebView and have a subclass of NSURLCache with a custom implementation to help serve these files.
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
I am using this to hijack the requests and return a locally stored copy of the files (mainly t
The body of cachedResponseForRequest:request (without the boiler plate) is essentially:
// logic to figure out what the local file is, load it into a NSData object (f)
NSURLResponse *r = [[NSHTTPURLResponse alloc] initWithURL:request.URL MIMEType:mimetype expectedContentLength:[f length] textEncodingName:nil];
NSCachedURLResponse *cr = [[NSCachedURLResponse alloc] initWithResponse:r data:f] ;
[super storeCachedResponse:cr forRequest:request];
return cr;
This works correctly for all of the cached content apart from a single svg image. When the svg image is attempted to load it will proceed through cachedResponseForRequest:request, build a NSCachedURLResponse object and return it.
However the next thing the application does is download the file from the remote server and then any subsequent request are served from the cache. We dont want to download the file remotely as its relatively large and impacts performance.
Does the NSURLCache deal with .svg files differently to other filetypes?
The maximum size of files that will be handled by the NSUrlCache can be influenced by the parameters when you initialize the cache with:
initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)diskPath
The maximum file size is related to the memoryCapacity. You should set that to at least 10 (why?) times the maximum file size that you want to handle.
I have tried this with files up to 15MB using https://github.com/evermeer/EVURLCache

Resources