3
votes

I am able to see cached responses when I query NSURLCache directly but when I request the same resource through NSURLSession:dataTaskWithRequest it always queries the server and never gives me the cached response, even with internet disabled.

I configure NSURLCache in application:didFinishLaunchingWithOptions like this:

let URLCache = NSURLCache(memoryCapacity: 20 * 1024 * 1024,
                          diskCapacity: 80 * 1024 * 1024, diskPath: nil)
NSURLCache.setSharedURLCache(URLCache)

Then I am using this code to check the cache and fetch the response:

print("cached response is \(NSURLCache.sharedURLCache().cachedResponseForRequest(request)?.response)")

NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
  print("nsurlsession response \(response)")
}.resume()

The result of my debug prints are:

cached response is Optional(<NSHTTPURLResponse: 0x7f809d0ddfe0> { URL: https://api.test.com/stuff } { status code: 200, headers {
"Cache-Control" = "public, s-maxage=600";
Connection = "keep-alive";
"Content-Type" = "application/json";
Date = "Tue, 06 Sep 2016 23:41:24 GMT";
Etag = "\"4800a836fee27c56a3cce1f0f2bddaefa5a7785b\"";
Server = "nginx/1.11.3";
"Transfer-Encoding" = Identity;
"X-RateLimit-Limit" = 60;
"X-RateLimit-Remaining" = 2; } })

nsurlsession response Optional(<NSHTTPURLResponse: 0x7f809d202e50> { URL: https://api.test.com/stuff } { status code: 200, headers {
"Cache-Control" = "public, s-maxage=600";
Connection = "keep-alive";
"Content-Type" = "application/json";
Date = "Tue, 06 Sep 2016 23:51:52 GMT";
Etag = "\"4800a836fee27c56a3cce1f0f2bddaefa5a7785b\"";
Server = "nginx/1.11.3";
"Transfer-Encoding" = Identity;
"X-RateLimit-Limit" = 60;
"X-RateLimit-Remaining" = 52; } })

As you can see, the Cache-Control headers from the server are set to allow caching. I am not doing anything special with my request, just creating a default NSURLRequest with a URL. Each time I trigger the request, the new response gets cached, but it is never retrieved on subsequent requests.

Is there any reason why NSURLSession would not use a response stored in NSURLCache? Is there something I must do to tell NSURLSession to actually look up requests in the cache?

1

1 Answers

11
votes

I can't tell you with absolute certainty why the cache isn't being consulted, but I can give you a list of the most likely reasons:

  • The server did not respond with 304 when queried about the validity of that ETag header (IIRC using a HEAD request).
  • The request is too big—either relative to the size of the buffer or in absolute terms. The cache should at least be a couple of orders of magnitude bigger than the requests that you would typically cache; anything over about 5% of the cache size will not be cached.
  • The request method is something other than GET. (Only GET requests are cached unless you monkey with the machinery significantly.)
  • More than 10 minutes have elapsed (600 seconds isn't very long).
  • The request was made in a different URL session that has a different backing cache.
  • The request was made in an ephemeral URL session or a session that for some other reason has no cache.
  • The session actually is returning the cached response, but you're seeing a request because it is revalidating a little more aggressively than you might expect—possibly because it will reach its maximum age so soon.
  • Your URL request is getting handled in the background by a custom NSURLProtocol that doesn't respect the cache (e.g. because of some badly behaved third-party networking or advertising framework).
  • The request had not actually been fully written to the cache when you tried to retrieve it (timing race caused by multiple threads).

I'm probably forgetting several others. With that said, if I'm forgetting them, that probably means that they aren't documented.

So...

If you verify that everything listed above is working as expected, file a bug at bugreporter.apple.com and include enough code to reproduce the problem, along with a packet dump if possible.