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

Borama Apps
Next Level Swift

--

Photo by Cleyder Duque from Pexels

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:

  1. Cache-Control is not set, and Etag 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, andEtagis not set.

We hit the server on every call, but URLSession returns the same asset each time.

5. Cache-Controlis 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!

--

--