HTTP Caching
Want more? Subscribers can view all 485 episodes. New episodes are released regularly.
Episode Links
- Source Code
- SDURLCache
- Charles Proxy
- Sample web app w/ API
- Heroku Dev Center article on iOS HTTP Caching
Inspecting the API with curl
Curl makes it easy to test out APIs and see what type of raw responses you'll receive.
Issue a normal request to the API:
curl -i http://cache-tester.herokuapp.com/contacts.json
You'll get something like this as a response:
HTTP/1.1 200 OK
Cache-Control: max-age=10, public
Content-Type: application/json; charset=utf-8
Date: Thu, 10 May 2012 12:51:49 GMT
Etag: "ba598025146eff37e26c9150b180a78b"
Last-Modified: Mon, 07 May 2012 01:55:57 GMT
Server: WEBrick/1.3.1 (Ruby/1.9.2/2011-07-09)
X-Request-Id: c6fa9ef1cd630a8bb1640d8a4480dca6
X-Runtime: 0.011148
X-Ua-Compatible: IE=Edge,chrome=1
Content-Length: 3413
Connection: keep-alive
[{"created_at":"2012-05-07T01:36:19Z","email":"honestabe@gmail.com","id":1,"name":"Abe Lincoln","updated_at":"2012-05-07T01:36:19Z"},{"created_at":"2012-05-07T01:36:38Z","email":"benf@gmail.com","id":2,"name":"Ben Franklin","updated_at":"2012-05-07T01:36:38Z"},{"created_at":"2012-05-07T01:37:39Z","email":"charlie_d@hotmail.com","id":3,"name":"Charles Darwin","updated_at":"2012-05-07T01:37:39Z"},{"created_at":"2012-05-07T01:37:58Z","email":"dougie_adams@aol.com","id":4,"name":"Douglas Adams","updated_at":"2012-05-07T01:37:58Z"},{"created_at":"2012-05-07T01:38:08Z","email":"enor@gmail.com","id":5,"name":"Edward Norton","updated_at":"2012-05-07T01:38:08Z"},{"created_at":"2012-05-07T01:38:23Z","email":"bourbonrocks@gmail.com","id":6,"name":"Frank Sinatra","updated_at":"2012-05-07T01:38:23Z"},{"created_at":"2012-05-07T01:38:39Z","email":"jarjar@aol.com","id":7,"name":"George Lucas","updated_at":"2012-05-07T01:38:39Z"},{"created_at":"2012-05-07T01:38:49Z","email":"modelt@gmail.com","id":8,"name":"Henry Ford","updated_at":"2012-05-07T01:38:49Z"},{"created_at":"2012-05-07T01:39:13Z","email":"revenge_fencer@gmail.com","id":9,"name":"Inigo Montoya","updated_at":"2012-05-07T01:39:13Z"},{"created_at":"2012-05-07T01:39:27Z","email":"jspringer@hotmail.com","id":10,"name":"Jerry Springer","updated_at":"2012-05-07T01:39:27Z"},{"created_at":"2012-05-07T01:39:48Z","email":"kbacon@gmail.com","id":11,"name":"Kevin Bacon","updated_at":"2012-05-07T01:39:48Z"},{"created_at":"2012-05-07T01:39:57Z","email":"wow@gmail.com","id":12,"name":"Leroy Jenkins","updated_at":"2012-05-07T01:39:57Z"},{"created_at":"2012-05-07T01:40:19Z","email":"airjordan@gmail.com","id":13,"name":"Michael Jordan","updated_at":"2012-05-07T01:40:19Z"},{"created_at":"2012-05-07T01:41:40Z","email":"nolte@gmail.com","id":14,"name":"Nick Nolte","updated_at":"2012-05-07T01:41:40Z"},{"created_at":"2012-05-07T01:41:51Z","email":"oprah@me.com","id":15,"name":"Oprah Winfrey","updated_at":"2012-05-07T01:41:51Z"},{"created_at":"2012-05-07T01:42:09Z","email":"paulaabdul@gmail.com","id":16,"name":"Paula Abdul","updated_at":"2012-05-07T01:42:09Z"},{"created_at":"2012-05-07T01:45:36Z","email":"quincy@gmail.com","id":17,"name":"Quincy Jones","updated_at":"2012-05-07T01:45:36Z"},{"created_at":"2012-05-07T01:45:48Z","email":"thered@aol.com","id":18,"name":"Robert Redford","updated_at":"2012-05-07T01:45:48Z"},{"created_at":"2012-05-07T01:46:28Z","email":"karate@gmail.com","id":19,"name":"Steven Seagal","updated_at":"2012-05-07T01:46:42Z"},{"created_at":"2012-05-07T01:48:50Z","email":"ulysses@aol.com","id":21,"name":"Ulysses S Grant","updated_at":"2012-05-07T01:48:50Z"},{"created_at":"2012-05-07T01:49:15Z","email":"vighug@hotmail.com","id":22,"name":"Victor Hugo","updated_at":"2012-05-07T01:49:15Z"},{"created_at":"2012-05-07T01:49:28Z","email":"pennypincher@aol.com","id":23,"name":"Warren Buffet","updated_at":"2012-05-07T01:49:28Z"},{"created_at":"2012-05-07T01:50:32Z","email":"xrxs@gmail.com","id":24,"name":"Xerxes","updated_at":"2012-05-07T01:50:32Z"},{"created_at":"2012-05-07T01:51:47Z","email":"yanni@aol.com","id":25,"name":"Yanni","updated_at":"2012-05-07T01:51:47Z"},{"created_at":"2012-05-07T01:52:14Z","email":"lightning@hotmail.com","id":26,"name":"Zeus","updated_at":"2012-05-07T01:52:14Z"},{"created_at":"2012-05-07T01:47:00Z","email":"radiohead@me.com","id":20,"name":"Thom Yorke","updated_at":"2012-05-07T01:55:57Z"}]
Make a note of the Etag and Last Modified headers in the response above.
Issue a conditional GET request with the Etag
Using the etag noted above, you can now make the same request but provide a conditional GET header called If-None-Match
.
curl -i -H "If-None-Match: \"ba598025146eff37e26c9150b180a78b\"" http://cache-tester.herokuapp.com/contacts.json
Make sure to escape the inner quotes surrounding the etag
You should get a 304 in response, with no content.
Issue a conditional GET request with the Last Modified Date
Depending on the scenario, it may make sense to use the last modified date instead of the etag (most clients will just support both). To do this, you'll send an If-Modified-Since
header with the UTC date you noted from above.
curl -i -H "If-Modified-Since: Mon, 07 May 2012 01:55:57 GMT" http://cache-tester.herokuapp.com/contacts.json
Again, you should receive a 304.
When the data changes
Note that if you make a change to one of the contacts in the system, you will no longer receive a 304 and will have to make a note of the new Etag and Last Modified Date.
Using NSURLCache and SDURLCache
NSURLCache
is built-in, however does not support disk caching on iOS 4. If you want your content to persist across application launches, then you'll want to leverage disk caching.
SDURLCache
is a drop in replacement for NSURLCache
but does save to disk on iOS 4 and above.
The API above returns a 10 second Cache-control
header, which is below the 5 minute threshold that SDURLCache
considers reasonable to cache to disk. In order to see this disk caching work for such a small cache duration, you'll have to set a property called minCacheInterval
, which accepts an NSTimeInterval in seconds:
- (void)prepareCache {
SDURLCache *cache = [[SDURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:20 * 1024 * 1024
diskPath:[SDURLCache defaultCachePath]];
cache.minCacheInterval = 0;
[NSURLCache setSharedURLCache:cache];
NSLog(@"Cache is being logged to: %@", [SDURLCache defaultCachePath]);
}
How NSURLCache is used
If you've set up your NSURLCache
(or SDURLCache
) then any NSURLConnection
based request should automatically leverage the cache. If you need to control this, for example to prevent caching a large file that you know won't be requested again, then you'll have to implement the protocol method:
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
// can return a customized NSCachedURLResponse or nil to prevent caching for this response
return nil;
}
If you're using AFNetworking, which wraps these classes, you'll have to set the cacheResponseBlock
property that has the same signature:
[operation setCacheResponseBlock:^NSCachedURLResponse *(NSURLConnection *connection, NSCachedURLResponse *cachedResponse) {
// handle here and return NSCachedURLResponse (or nil)
return nil;
}];