10

According to the documentation if you use the default useProtocolCachePolicy the logic is as follows:

  1. If a cached response does not exist for the request, the URL loading system fetches the data from the originating source.
  2. Otherwise, if the cached response does not indicate that it must be revalidated every time, and if the cached response is not stale (past its expiration date), the URL loading system returns the cached response.
  3. If the cached response is stale or requires revalidation, the URL loading system makes a HEAD request to the originating source to see if the resource has changed. If so, the URL loading system fetches the data from the originating source. Otherwise, it returns the cached response.

However, my experimentation (see below) has shown this to be completely false. Even if the response is cached it is never used, and no HEAD request is ever made.

Scenario

I am making a request to a URL that returns ETag and Last-Modified headers which never change. I have made the request at least once so the response is already cached (which I can verify by looking at the cache DB for the app on the iOS simulator)

Using useProtocolCachePolicy (the default)

If I have a URLSession with a URLSessionConfiguration with requestCachePolicy set to useProtocolCachePolicy then the response is cached (I can see it in the cache DB), but the cached response is never used. Repeated requests to the same URL always make a new GET request without If-None-Match or If-Modified-Since headers, so the server always returns HTTP 200 with the full response. The cached response is ignored.

Using reloadRevalidatingCacheData on every URLRequest

If I set the cachePolicy on each URLRequest to reloadRevalidatingCacheData then I see caching in action. Each time I make the request, a GET request is made with the If-None-Match and If-Modified-Since headers set to the values of the ETag and Last-Modified headers, respectively, of the cached response. As nothing has changed, the server responds with a 304 Not Modified, and the locally cached response is returned to the caller.

Using reloadRevalidatingCacheData only on the URLSessionConfiguration

If I only set requestCachePolicy = . reloadRevalidatingCacheData on the URLSessionConfiguration (instead of on each URLRequest) then when the app starts only the first request uses cache headers and gets a 304 Not Modified response. Subsequent requests are normal GET requests without any cache headers.

Conclusion

All the other cache policy settings are basically variants of "only use cached data" or "never use the cache" so are not relevant here.

There is no scenario in which URLSession makes a HEAD request as the documentation claims, and no situation in which it just uses cached data without revalidation based on expiration date information in the original response.

The workaround I will use is to set cachePolicy = .reloadRevalidatingCacheData on every URLRequest to get some level of local caching, as 304 Not Modified response only return headers and no data so there is a saving of network traffic.

If anyone as any better solutions, or knows how to get URLSession working as documented, then I would love to know.

mluisbrown
  • 14,448
  • 7
  • 58
  • 86

1 Answers1

1

Service response headers should include:

Cache-Control: must-revalidate

Apple will use this instruction to implement .useProtocolCachePolicy as described in documentation.

Alexander Karpov
  • 530
  • 1
  • 7
  • 16