Offline mode using NSURLCache - ios

I am using AFNetworking with NSURLCache. Requests are created with NSURLRequestUseProtocolCachePolicy. App receives responses with headers:
Etag = "1398684731";
Cache-Control = "public";
and everything works perfect. But now I need to add an offline mode.
How it should work:
If internet connection is available everything should work as usual
(application asks back-end for new data and if it has different
Etag new data is downloaded if Etag is the same - cached response
is used).
If there is no internet connection - cached response is
used.
The problem is that in offline mode requests fail.
I have tried to solve this using various Cache-Control options but it seems that it does not work this way.
I have found possible solution here https://stackoverflow.com/a/15885318/3140927 . It should work but it is not very elegant and I think something might have changed in the last year.
Also I have found that "NSURLCache was not made for explicit offline scenarios and that it was designed to speed up Safari and should not be used for manual downloads". Will SDURLCache be better for my purposes?
So what could be the best way to implement offline mode?

It sounds like you want the request to succeed, even though the cache says the data has expired and should be retrieved from the server. You may have some luck setting the cache policy (different policy for online vs. offline) of certain requests where you'd rather use stale data than fail.
Great Link is here->
SDURLCache with AFNetworking and offline mode not working

Today I tried to solve exactly the same problem with this article and it works pretty well: http://blog.originate.com/blog/2014/02/20/afimagecache-vs-nsurlcache/. The key point is here to subclass NSURLCache to make your cache independent from response headers.
I've found couple mistypes in examples, but hope it still will be helpful for you!

Related

iOS: Are there any issues that can occur when setting cache policy to no?

I don't want my App storing any URL data in the cache.db file. I took advice on StackOverflow and set the cache URL policy to NSURLRequestReloadIgnoringLocalCacheData like this.
[manager.requestSerializer setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
However, I'm not sure if there are any issues that can arise from doing this. Any tips or suggestions are appreciated.
Edit: I haven't ran into any issues regarding requests or the App in general, but I like to make sure.
There won't be any issues with this, however you have to ensure, that the named behaviour is really the one you want. E.g. you have a caching mechanism implemented somewhere else.
This policy does exactly what is supposed to do. It ignores any local cache data and loads any data directly from a remote source. However, you're not protected from any intermediate caching mechanisms.
You can include proper headers to your request to omit any intermediate caching (Cache-Control: no-cache to tell some proxies what do you want to do with the request for example)

URLSession caching even after app restart

I've just come across something that has entirely changed my mental image of URLSession caching in iOS.
We were hitting an endpoint that only ever got hit once.
Restarting the app wouldn't hit the endpoint again.
Deleting the app would cause it to hit the endpoint again... but only once.
The header of the response contains...
Cache-Control public, max-age=1800
So it is down to caching. By manually telling the URLSession to ignore the cache it would hit the endpoint again.
In the docs it shows the caching policy and how it works as a workflow diagram.
https://developer.apple.com/documentation/foundation/nsurlrequestcachepolicy/nsurlrequestuseprotocolcachepolicy
But where is the cached data stored once the app is terminated? Surely the app and everything to do with it is removed from memory?
The URLSession is using URLCache for it's caching system. It's used for all network resources. You can access it directly or setting your own through URLSessionConfiguration. The underlying location of the URLCache is on the file system rather than in memory. There is a way to manage cache yourself though. Say, for instance, your response should be encrypted on the device. Slightly bad example, but you get the point. ;)
Here's an article how to manage cache programmatically if you are needing more control over caching.

Users are "logging in as others" on Chrome for iOS

We're having a unique issue that is affecting a small handful of users from around the world. Nothing connects them aside from the fact they are all using Chrome for iOS.
Intermittently, users will login to our application (https://www.mousehuntgame.com) and appear to be "someone else". This issue cropped up recently during a period when no new code had been pushed to the site.
Of course the first thing we checked was that our authentication was not bugged or that the user's hash (stored in either cookies or a PHP session) was not crossing connections somewhere. The issue is not in the authentication system, and it only affects users using Chrome for iOS. The same users using Safari no longer see the issue.
We have the following PHP headers being sent to prevent caching:
header("Cache-Control: no-cache, no-store, max-age=0, must-revalidate, private");
header("Pragma: no-cache");
The "target users" that these users "turn into" are not yet confirmed to be also using Chrome. The solution for them to simply stop using the browser is not an option as others who continue to use Chrome can still gain access to these accounts.
Can Chrome be somehow caching cookies and "sharing" them across users? Could this be a DNS issue where it sees a mobile user agent and in order to save loading time it retrieves cached information and hands it off without further checking who the user is? This is a stretch, I know, but it's been a strange issue and we're grasping at straws now.
I work on the Chrome Data Compression proxy.
I'd be very surprised if the Chrome proxy were at fault here, since we respect standard caching headers. That said, there could be a bug. If you can try to reproduce with and without the proxy that would be helpful. Another way to test is to open the page in an Incognito tab (which does not use the proxy).
(Edited)
I looked at some of the headers we are seeing from your site, and they include things like
Cache-Control: max-age=2592000
which means these responses are publicly cacheable for 30 days. I see a wide range of caching headers from many different URLs on the site, suggesting that your caching rules aren't being applied as widely as you thought; but of course I don't know the structure of the site and whether that would lead to the problem you are describing.
Feel free to reach out (email is fine too) and I'm happy to help debug if you still think this is a problem on our end.

NSURLCache on iOS 4.3.x not checking Last-Modified or Etag headers

If I download a document using NSURLConnection/NSURLCache that gets cached, edit that document on the server (so Last-Modified and Etag headers change) and then download the document again, the previously cache version is returned. NSURLCache/NSURLConnection makes no attempt to check for a newer resource using If-Modified-Since/If-None-Match headers in the request (which would return a newer version of the resource).
SHould NSURLCache used in conjunction with NSURLConnection check for an updated resource on the server using Last-Modified/Etag headers that have been previously cached? I can't seem to find any documentation to say whether this should happen or if checking for HTTP 304 content is up to the developer.
I'll let other people comment on how to use NSURLCache. I found that the most reliable way to prevent caching with NSURLConnection, proxy servers, and misconfigured web servers, was to append an incrementing number to your URL.
So rather than using http://mycompany.com/path, use http://mycompany.com/path?c=1, http://mycompany.com/path?c=2, http://mycompany.com/path?c=3, etc, etc.
It's a hack, but a good one.

'Vary: If-None-Match' to cache mobile and desktop requests separately

Note: Please correct me if any of my assumptions are wrong. I'm not very sure of any of this...
I have been playing around with HTTP caching on Heroku and trying to work out
a nice way to differentiate between mobile and desktop requests when caching using Varnish
on Heroku.
My first idea was that I could set a Vary header so the cache is Varied on If-None-Match. As Rails automatically sends back etags generated from a hash of the content the etag would vary between desktop and mobile requests (different templates) and so it would eventually cache two versions (not fact, just my original thoughts). I have been playing around with this but I don't think it works.
Firstly, I can't wrap my head around when/if anything gets cached as surely requests with If-None-Match will be conditional gets anyway? Secondly, in practice fresh requests (ones without If-None-Match) sometimes receive the mobile site. Is this because the cache doesn't know whether to serve up the mobile or desktop cached version as the If-None-Match header isn't there?
As it probably sounds, I am rather confused. Will this approach work in any way or am I being silly? Also, is there anyway to achieve separate cached versions if I am unable to reach the Varnish config at all (as I am on Heroku)?
The exact code I am using in Rails to set the cache headers is:
response.headers['Cache-Control'] = 'public, max-age=86400'
response.headers['Vary'] = 'If-None-Match'
Edit: I am aware I can use Vary: User-Agent but trying to avoid it if possible due to it have a high miss rate (many, many user agents).
You could try Vary: User-Agent. However you'll have many cached versions of a single page (one for each user agent).
An other solution may be to detect mobile browsers directly in the reverse proxy, set a X-Is-Mobile-Browser client header before the reverse proxy attempts to find a cached page, set a Vary: X-Is-Mobile-Browser on the backend server (so that the reverse proxy will only cache 2 versions of the same page) and replace that header with Vary: User-Agent before sending to client.
If you can not change your varnish configuration, you have to make different urls for mobile and desktop pages. You can add some url-parameter (?mobile=true), add a piece in your path (yourdomain.com/mobile/news) or use a different host (like m.yourdomain.com).
This makes a lot of sense because (I've seen this many times, both in CMSs and applications) at some point in time you want to differentiate content and structure for mobile devices. People just do different things or are looking for different information on mobile devices...

Resources