NSURLCache is some kind of a dark art magic that is neither documented properly, nor behaves as expected.
I am using AFNetworking's AFHTTPSessionManager to utilise NSURLSession. I am using this mocking service to create custom HTTP responses.
I am setting the NSURLCache with disk capacity and all the cache policies.
Then in the URLSession:task:didCompleteWithError method of NSURLSessionTaskDelegate, I am doing this:
NSURLRequest *request = task.currentRequest;
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
The documentation states about the cachedResponseForRequest:
#result The NSCachedURLResponse stored in the cache with the given
request, or nil if there is no NSCachedURLResponse stored with the
given request.
AFNetworking's FAQ states:
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.
After all this my assumption is that the value of the the cachedResponse variable will be nil if the Cache-Control is NOT set properly. However, whatever I have tried to do so far - setting it to 'private', 'no-cache' or whatever - the cachedResponse ALWAYS contains the cached response. How can I verify that the caching mechanism works as expected ? Is there anything that I am missing from the setup ?
I have been doing my tests by firstly making the request online, then switching off the internet on my computer. XCode 7.0 beta iOS 9.0.
Any help is appreciated.
Related
I would like to determine if the response from NSURLSessionDataTask came from cache, or was served from server
I'am creating my NSURLSessionDataTask from
request.cachePolicy = NSURLRequestUseProtocolCachePolicy;
Two easy options come to mind:
Call [[NSURLCache sharedURLCache] cachedResponseForRequest:request] before you make the request, store the cached response, then do that again after you finish receiving data, and compare the two cached responses to see if they are the same.
Make an initial request with the NSURLRequestReturnCacheDataDontLoad cache policy, and if that fails, make a second request with a more sane policy.
The first approach is usually preferable, as the second approach will return data that exists in the cache even if it is stale. However, in some rare cases (e.g. an offline mode), that might be what you want, which is why I mentioned it.
In order to know whether an URLSessionDataTask response comes from the cache or the network, you have to use a custom URLSession and provide it with an URLSessionTaskDelegate which has to implement the following method:
func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics)
You'll find very useful information in metrics, especially the list of transactions metrics for the request. Each transaction metrics has a property resourceFetchType which can either be .localCache, .networklLoad, .serverPush or .unknown.
More information here: Apple documentation regarding URLSessionTaskDelegate
If your information need is only out of curiosity you can view your network usage the Xcode runtime network metics. Change the policy to a different setting and observe the difference.
If you want to use caching then it's not a good answer to disable caching on the client.
if the server has set cache headers (etag, cache-control="no-store") then NSURLSession will revalidate and serve you cached / fresh response based on the 200 / 304 response from the server. However in your code you will always see statusCode 200 regardless of if NSUrlSession received 200 or 304. This is limiting, because you may want to skip parsing, re-creating objects etc when the response hasnt changed.
THe workaround I did, is to use the etag value in the response to determine if something has changed
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;
NSString *etag = (httpResp && [httpResp isKindOfClass:[NSHTTPURLResponse class]]) ? httpResp.allHeaderFields[#"Etag"] : nil;
BOOL somethingHasChanged = [etag isEqualToString:oldEtag];
I need response headers of the request URL that I need to recieve and process in UIWebview Delegate method webViewDidFinishLoad: ? But I don't understand how to achieve it. Please note that I don't need request headers but response headers.
One idea is to create a NSUrlConnection and fire the request again to recieve it in completion block but I don't want to make two calls. There must be some way that it is handled by UIWebView and response headers can be retrieved . I just don't know how to go about it. Any ideas are welcome.
Try this:
NSCachedURLResponse *resp = [[NSURLCache sharedURLCache] cachedResponseForRequest:webView.request];
But this only works for cacheable urls!
I'm using AfNetworking, I make a call for new data but I keep getting the cached result back. So if I'm in a VC that's shows the data, pop back to the root and change data on my server, I then wait 30sec and when I push back into the VC I'll see the the old data. If I hit the URL in a browser I see the correct data. If I re-run the app I will see the changes to the data.
My response from my server sends back cache control header: Cache-Control:max-age=10, public
From what I can tell is that I always always get a NSCachedURLResponse back and that the cache is not listening to my cache-controll policy.
In my AppDelegate I set my SharedURLCache:
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:8 * 1024 * 1024 diskCapacity:8 * 1024 * 1024 diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
How I set the URLRequest:
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
I do nothing else involving NSURLCache.
Any ideas?
BooRanger
The behavior you describe is exactly what I'd expect from the code you've posted. Have you tried using NSURLRequestReloadIgnoringLocalAndRemoteCacheData instead of NSURLRequestUseProtocolCachePolicy? According to this NSURLRequestUseProtocolCachePolicy means:
Specifies that the caching logic defined in the protocol
implementation, if any, is used for a particular URL load request.
This is the default policy for URL load requests.
So if you want it to always load from the server, you should use NSURLRequestReloadIgnoringLocalAndRemoteCacheData which is described as:
Specifies that not only should the local cache data be ignored, but
that proxies and other intermediates should be instructed to disregard
their caches so far as the protocol allows.
I'm looking for a network caching solution for my iOS application that is persistent across launches. I started to read about NSURLCache, but didn't see any mention regarding persistence. Does anyone know how this behaves when you use NSURLCache then close and open the app? Does it persist?
NSURLCache automatically caches requests for requests made over NSURLConnection and UIWebViews according to the cache response from the server, the cache configuration, and the request's cache policy. These responses are stored in memory and on disk for the lifetime of the cache.
Aside
I validated the behavior with the following code. You do not need to use any of the below in your own code. This is just to demonstrate how I confirmed the behavior.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Prime the cache.
[NSURLCache sharedURLCache];
sleep(1); // Again, this is for demonstration purposes only. I wouldn't do this in a real app.
// Choose a long cached URL.
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://cdn.sstatic.net/stackoverflow/img/favicon.ico"]];
// Check the cache.
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
NSLog(cachedResponse ? #"Cached response found!" : #"No cached response found.");
// Load the file.
[NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
return YES;
}
The code does the following:
Primes the cache. I noticed a behavior where the cache would not return the cached result until the cache had been initialized and had a chance to scan the disk.
Creates a request for a long-cached file.
Checks if a response exists for the URL and displays the status.
Loads the URL.
On first load, you should see "No cached response found." On subsequent runs, you will see "Cached response found!"
I'm hoping someone can shed some light on a few things I've been researching but not making much progress on.
I'd like to take advantage of NSURLCache to return cached responses for API calls that I make within an iOS App. When the device is online, I'd like to return the cached response if it's recent enough, otherwise fetch from remote. When the device is offline I'd like to immediately return the cached response (if any) regardless of it's age.
I'm using AFNetworking. The API calls that I'm making are to a server I control. Protocol is HTTPS. The Cache-Control response header is currently "max-age=0, public". I'm not currently setting cache related request headers (should I?). I set the request's cache policy to be NSURLRequestReturnCacheDataDontLoad when offline and use the default NSURLRequestUseProtocolCachePolicy when online. And I can see the requests and their responses in the default Cache.db on disk. However when I'm offline all requests fail (no cached responses (despite appearing to have been cached) are being used/returned.
Per http://nshipster.com/nsurlcache/ I initialize a sharedURLCache in didFinishLaunchingWithOptions and set the AFNetworking setCacheResponse block to something like this:
NSMutableDictionary *mutableUserInfo = [[cachedResponse userInfo] mutableCopy];
NSMutableData *mutableData = [[cachedResponse data] mutableCopy];
NSURLCacheStoragePolicy storagePolicy = NSURLCacheStorageAllowed;
return [[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response] data:mutableData userInfo:mutableUserInfo storagePolicy:storagePolicy];
I've read these and other posts on the topic:
http://petersteinberger.com/blog/2012/nsurlcache-uses-a-disk-cache-as-of-ios5/
http://blackpixel.com/blog/2012/05/caching-and-nsurlconnection.html
I'm wondering if anyone out there has successfully achieved this functionality before using the standard NSURLCache (also interested in success stories involving SDURLCache but Peter S. says as of iOS5 disk caching is supported therefore SDURLCache is no longer needed, ie I'd like to use the default built in cache).
Thanks in advance!
Did you see this post?
AFNetworking (AFHttpClient) offline mode not working with NSURLRequestReturnCacheDataDontLoad policy
Looks like it might be a bug with iOS 6. Here is what Robert Mao had to say in the post:
A summarized work around is read the response from cache, instead of
use NSURLRequestReturnCacheDataDontLoad policy:
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (cachedResponse != nil &&
[[cachedResponse data] length] > 0)
{
// Get cached data
....
}
Unless all of your calls are 100% GET and free of side effects or time dependency then this is dangerous.