How to check if UIWebView loads from NSURLCache? - ios

I actually have more than one question regarding this... I'm still new to NSURLCache and UIWebViews but somehow got the basics down I think.
Anyway, I was able to successfully implement Caching for my UIWebView(I think). I initialized the NSURLCache. My UIWebView loads its contents with the loadRequest: method. I assigned the ViewController that owns the UIWebView as it's delegate. I also had the ViewController implement NSURLConnectionDataDelegate. I implemented the connection:willCacheResponse: method just to log a message in the console so I could check that caching was done. I also logged the current disk and memory usage. It increases after the caching so I can see that it did work.
However, when I load the page again, I think the request is sent again to the server because the connection delegate methods like connection:didReceiveResponse: and connection:willCacheResponse are being called.
I also wanted to try getting the cachedResponse from the cache by using cachedResponseForRequest:. I called this method in webViewDidFinishLoad: but it always returns null. Did caching really work?
How can you tell if the WebView loaded from the cache? Am I misusing cachedResponseForRequest:? And on another note, what proper Control-Cache header values should be present in the very least for caching? I'm testing on google's homepage URL and it returns the private value for its Cache Control header but I can see that caching works because the connection:willCacheResponse: is called.
Help please?

If you run a HTTP proxy like Charles or Fiddler you can keep tabs on what network traffic is actually going out over the wire(less).
You can also inspect the request/response headers to see whether the response is indeed destined for the cache.
Note that in some cases you might see a conditional-GET (returns a 304) meaning the client asked for content that it has in its cache, but only wants to download the content if the server has a more recent version.

Related

NSURLCache cachedResponseForRequest getting call twice

In my code i had override NSURLCache cachedResponseForRequest to send the cached response in case if downloaded already. But for some reason cachedResponseForRequest getting called twice. Could anybody know the reason?
Is this a default behaviour?
I'm pretty sure that the method in question is called from two different parts of the NSURLSession stack for very different reasons:
To get the date of the cached response, so that it can provide headers in a HEAD request asking the server if the content has been modified since then.
To obtain the actual data for the cached response after the server sends back a 304 (NOT MODIFIED) status code.

NSURLCache caching with different POST requests

I am using NSURLSession for networking and making POST requests to a server. I want to be able to cache these requests, however the URL is always the same.
Is it possible to cache with NSURLCache and change the cache key to something unique such as the request body?
By default, IIRC, POST requests are not cached at all. But yes, you can certainly do it. IIRC, on the NSURLSession side, the only thing you can control is whether the request gets cached or not. To actually control the name under which it is cached, you'll need to implement a custom URL protocol. I've never done what you're trying to do, but I'm pretty sure you'd do it roughly as follows:
Create an NSURLProtocol subclass, and provide it via the protocolClasses property on your session configuration. From that point on, your class gets called first whenever any that session makes a URL request.
In that subclass, in your startLoading method, use setProperty:forKey:inRequest: to tag the request as having been processed by your protocol.
In your canInitWithRequest method, call propertyForKey:inRequest: to see if the request is already tagged, and if so, return NO. That way, you'll see each request exactly once.
In your startLoading method, start a new data task with the tagged request.
In stopLoading, cancel the task.
In canonicalRequestForRequest:, do your conversion. You might have to use setProperty:forKey:inRequest: to store the original, unmodified URL, just in case you get back the modified URL. (I'm not sure.)
I don't think you'll need to implement requestIsCacheEquivalent:toRequest:, but keep it in mind, just in case I'm wrong. You might be able to implement that instead of the canonicalization method, also.

Get cached Data of webpage in my iOS app

I am using NSUrlConnection to grab some data from a web service that returns JSON data when I hit a url but sometimes the page goes down or something is wrong.
Is there a way to use the cached version of the url in my iOS App to grab that data when the webpage returns a 404?
In the browser if I go to cache:mysite.com/test.php I can see the JSON data even when the page is down but when I try to use the same url in my iOS App, I do not get the JSON data back.
Is there maybe an Obj C class I am not aware of or an option for the NSUrlConnection?
NSURLConnection already uses a cache. By default it only caches responses in-memory, you can customize this by setting it to also use on-disk storage:
NSURLCache *result = [[NSURLCache alloc] initWithMemoryCapacity:[(1024*1024*512) diskCapacity:(1024*1024*1024 * 100) diskPath#"Cache.db"]:
[NSURLCache setSharedURLCache:result];
The reason you are seeing the behavior you describe in your question is that the remote web service is telling your client not to cache the response. You can check this using REDbot or a tool like Charles. By default NSURLRequest will use the protocol's caching policy and semantics, which is almost always the correct thing to do. You can specify a different cache policy by changing the cachePolicy property of the NSURLRequest.
After much search, I didn't find a way to get a cached version of a web page in my iOS app but I handled the 404 error by looking for the response status code and using a service like Google Cache or archive.org/web/ to get the cached version of the url if a 404 was found.
Might not be the most delicate way but I could not find another way to get a cached version of the website when making the request.

iOS NSURLCache making up HTTP 404 when going through NSURLProtocol

If you are serious about answering this question, please clone the mini app i have created and see if it misbehaves for you the same way, before speculating about the answer, thank you :)
https://github.com/pavel-zdenek/nsurlprotocol-testbed
A very simple browser: UIWebView with all requests going through bare bones NSURLProtocol reimplementation. It elapses the loading time. The switch un/registers the protocol handler at runtime. Now load one specific site: http://www.rollingstone.com. Besides tons of other resources, the page GETs
http://sjc.ads.nexage.com/js/admax/admax_api.js
which produces an XHR request for
http://sjc.mediation.nexage.net/admax/adMaxApi.do?dcn=<somehash>
PZProtocolHandler OFF: XHR loads instantly. Observed as HTTP 200 on the wire(shark), reflected as successful load in Safari Web Inspector. On further loads, it's not on the wire anymore but still HTTP 200 in Web Inspector. In other words, cached.
PZProtocolHandler ON: Web Inspector reports 3 tries, timing out after 10s each, resulting in HTTP 404. No change with further reloads. Now the interesting part: according to wire(shark), the request goes out and is responded with proper HTTP 200. Furthermore, all 3 tries are sequenced on one and the same TCP channel. "Someone" is making up 404 along the way to UIWebView.
Now, even before reading clever blogs, i found out that iOS URL caching goes thermonuclear when the server response does not contain Cache-Control header. And this particular GET request is such case. But it should not result in 404 failure, should it? Also i have tried to hibernate the default caching through various methods: implementing caching-related delegate calls in NSURLProtocol, creating new request with cache prohibiting flags, using expectedly top knowledge in SDURLCache, even replacing NSURLCache with what i think should be a "null cache". See everything in the project sources.
The 404 persists for me. I doubt that the mere fact of overriding NSURLProtocol would break things so much. I could live without an opportunity to fill a radar for that, thank you. I still hope that i am doing something wrong.
The issue isn't exactly solved, more like narrowed and cleared a little bit. If a new question develops, it will be most probably very different, so i'm self-answering this one.
So it probably isn't a cache issue. The site loads normally when explicit NSOperationQueue is assigned:
_connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:NO];
// comment out for sure stalling
[_connection setDelegateQueue:[NSOperationQueue mainQueue]];
[_connection start];
I would be tempted to prefer currentQueue, but that's nil at the moment of [NSURLProtocol startLoading]. Which according to Apple doc should mean that startLoading isn't a NSOperation itself, hence there is no queue.
Explicit routing to main thread obviously brings a different performance bottleneck: the delegate calls are stalled when UI is being interacted with, most exemplary when partially loaded page is attempted to be scrolled. While this is also an issue, it's something specific i can fill Apple TSI with. Any new knowledge will go to the github repo.
UPDATE:
Now the issue is fully solved by doing yet another simple thing: create a new vanilla NSOperationQueue and set the NSURLConnectionDelegate queue to it:
[_connection setDelegateQueue:_theNewlyCreatedQueue];
You aren't going to see this in any of the numerous NSURLProtocol howtos on the internets. It would be inappropriately self confident to claim that i know why it improves things so much, but now the offending website loads almost as smoothly as in Mobile Safari (within expected JavaScript execution speed difference) so the problem is solved for me.

Handle wifi issue with stringWithContentOfUrl

While testing my application, i connected to a wifi network which needed an authentication to access the internet.
I would have like [NSString stringWithContentOfUrl:encoding:error:] to fail or return the content of this authentication page even if it is not the page I asked. But it keeps on trying to download, and never returns.
Do you have any solution to detect this kind of issue ?
I would recommend using NSURLConnection. When a redirect happens it will call the delegate method as mentioned here. Also with NSURLConnection you will have greater control in the future when you add additional features and content. Or should the router not do a redirect and just force you to a page, you will be able to use the NSURLConnection to download the content and parse it to determine if it is indeed the page you were looking for.
In that case you'll have to do some coding. Download the contents of the url via NSURLRequest -> NSURLConnection. Then via the NSURLConnection's delegate methods you can respond to the authentication challenge.

Resources