Deep dive into Apple’s URLRequest Cache Policies
Hands-On analysis of Apple’s Cache Policies working together with a Node.js Express Server to increase API efficiency and performance
There are only two hard things in Computer Science: cache invalidation and naming things.
— Phil Karlton
Fortunately for us, Apple helps us by providing robust and powerful cache policies. Policies we should use instead of building a custom (NSCache) solution!
In this post, we’ll explore different URLRequest cache policies and the consequences of using them. Those policies apply to both iOS and macOS. We’ll use a hands-on approach looking both on server and front-end sides.
To make things easy, the Node.js Express Server (the Backend) will return strings, and we’ll log the responses in the console. (Check out the source code for both Server and App at the bottom).
Cache Policy #1: useProtocolCachePolicy
The official Apple’s documentation shows a well-presented decision tree. https://developer.apple.com/documentation/foundation/nsurlrequest/cachepolicy/useprotocolcachepolicy.
When looking at the responses, we are looking for both Cache-Control and the HTTP Etag. There are a couple of scenarios we will look at:
Cache-Control
is not set, andEtag
is disabled:
The app did connect to the server, but URLSession ignored the updated response. It might not be what we expect as we can use Etag on the server to sync the app with the server after updating the assets on the server.
2. Cache-Control
is not set, andEtag
is changed every 2 seconds:
Now every time the Etag
changes, the URLSession gives us an updated asset.
3. Cache-Control
is set to 4 seconds, and Etag
is changed every time the server asset changes.
After receiving the first response with the max-age
header set to 4 seconds, the app was hitting the server every 3 seconds. Still, the server returned the URLSession response every second with the status code 200. Secondly, the asset was updated when fetched from the server.
4. Cache-Control
is set to 0 seconds, andEtag
is not set.
We hit the server on every call, but URLSession returns the same asset each time.
5. Cache-Control
is set to 0 seconds (or not set at all), and Etag
changes on each incoming request
We hit the server on every call, and URLSession gives us the most recent asset version. This is the same behavior we would get from using the CachePolicy reloadIgnoringLocalCacheData
, always returning the latest asset version.
Cache Policy #2: returnCacheDataElseLoad
Apple’s documentation says: “Use existing cache data, regardless of age or expiration date, loading from originating source only if there is no cached data.”
When we use this policy, no matter how we set (or not set) both Etag
and Cache-Control
on the server, we’ll get just one “snapshot” of the asset, and the server will never be hit again unless we clear the local cache with for example:
URLCache.shared.removeAllCachedResponses()
It seems like URLRequest.CachePolicy.returnCacheDataElseLoad
is quite a dangerous policy!
Cache Policy #3: reloadIgnoringLocalCacheData
For this cache policy, Apple’s documentation says: “This policy specifies that no existing cache data should be used to satisfy a URL load request.”
No matter how we set Cache-Policy
header or Etag
, the server will be hit every second, and the latest asset will be returned. This policy should be handled with care to avoid overloading the server!
Cache Policy #4: returnCacheDataDontLoad
Apple describes the cache policy as: “If there is no existing data in the cache corresponding to a URL load request, no attempt is made to load the data from the originating source, and the load is considered to have failed. This constant specifies a behavior that is similar to an “offline” mode.”
And indeed, the results seen above are very similar to an offline mode. As you can see, our Express server was not hit by a request once!
Let’s hit our server using different cache policies at once.
Each policy needs to be directed to a unique endpoint. If the endpoints are the same, the cache will be overwritten every time we run a request with the policy URLRequest.CachePolicy.reloadIgnoringLocalCacheData
!
This is what happens if we hit the same endpoint using different caching policies:
The URLRequest response is overwritten, and the server is only hit once by URLRequest.CachePolicy.returnCacheDataElseLoad
and URLRequest.CachePolicy.useProtocolCachePolicy
requests.
Summary
In my opinion, we should use Apple’s caching capabilities, but some of the behaviors we analyzed above might not be so obvious after reading the documentation.
Below is the Swift code (OSX Command Line App):
And here’s our Nodejs server code:
We are always looking for talented and passionate Swift developers! Feel free to check out our writer's section and find out how you can share your knowledge with the Next Level Swift Community!