NSURLCache
NSURLCache
provides a composite in-memory and on-disk caching mechanism for URL requests to your application. As part of Foundation’s URL Loading System, any request loaded through NSURLConnection
will be handled by NSURLCache
.
Network caching reduces the number of requests that need to be made to the server, and improve the experience of using an application offline or under slow network conditions.
When a request has finished loading its response from the server, a cached response will be saved locally. The next time the same request is made, the locally-cached response will be returned immediately, without connecting to the server. NSURLCache
returns the cached response automatically and transparently.
As of iOS 5, a shared NSURLCache
is set for the application by default. Quoth the docs:
Applications that do not have special caching requirements or constraints should find the default shared cache instance acceptable. An application with more specific needs can create a custom NSURLCache object and set it as the shared cache instance using setSharedURLCache:. The application should do so before any calls to this method.
Those having such special caching requirements can set a shared URL cache in -application:did
on iOS, or –application
on OS X:
func application(application: UIApplication!, did Finish Launching With Options launch Options: NSDictionary!) -> Bool {
let URLCache = NSURLCache(memory Capacity: 4 * 1024 * 1024, disk Capacity: 20 * 1024 * 1024, disk Path: nil)
NSURLCache.set Shared URLCache(URLCache)
return true
}
- (BOOL)application:(UIApplication *)application
did Finish Launching With Options:(NSDictionary *)launch Options
{
NSURLCache *URLCache = [[NSURLCache alloc] init With Memory Capacity:4 * 1024 * 1024
disk Capacity:20 * 1024 * 1024
disk Path:nil];
[NSURLCache set Shared URLCache:URLCache];
}
Caching policies are specified in both the request (by the client) and in the response (by the server). Understanding these policies and how they relate to one another is essential to finding the optimal behavior for your application.
NSURLRequest Cache Policy
NSURLRequest
has a cache
property, which specifies the caching behavior of the request according to the following constants:
-
NSURLRequest
: Caching logic defined in the protocol implementation is used for a particular URL load request. This is the default policy.Use Protocol Cache Policy -
NSURLRequest
: Data should be loaded from the originating source. No existing cache data should be used.Reload Ignoring Local Cache Data -
NSURLRequest
: Not only should the local cache data be ignored, but proxies and other intermediates should be instructed to disregard their caches so far as the protocol allows.Reload Ignoring Local And Remote Cache Data -
NSURLRequest
: Existing cached data should be used, regardless of its age or expiration date. If there is no existing data in the cache corresponding to the request, the data is loaded from the originating source.Return Cache Data Else Load -
NSURLRequest
: Existing cache data should be used, regardless of its age or expiration date. If there is no existing data in the cache corresponding to the request, no attempt is made to load the data from the originating source, and the load is considered to have failed, (i.e. “offline” mode).Return Cache Data Dont Load -
NSURLRequest
: Existing cache data may be used provided the origin source confirms its validity, otherwise the URL is loaded from the origin source.Reload Revalidating Cache Data
It may not surprise you that these values are poorly understood and often confused with one another.
Adding to the confusion is the fact that NSURLRequest
and NSURLRequest
were not even implemented until iOS 13!
So here’s what you actually need to know about NSURLRequest
:
Constant | Meaning |
---|---|
use |
Default behavior |
reload |
Don't use the cache |
reload |
Seriously, don't use any caches along the way |
return |
Use the cache (no matter how out of date), or if no cached response exists, load from the network |
return |
Offline mode: use the cache (no matter how out of date), but don't load from the network |
reload |
Validate cache against server before using |
HTTP Cache Semantics
Because NSURLConnection
is designed to support multiple protocols—including both FTP
and HTTP
/HTTPS
—the URL Loading System APIs specify caching in a protocol-agnostic fashion. For the purposes of this article, caching will be explained in terms of HTTP semantics.
HTTP requests and responses use headers to communicate metadata such as character encoding, MIME type, and caching directives.
Request Cache Headers
By default, NSURLRequest
will use the current time to determine whether a cached response should be returned. For more precise cache control, the following headers can be specified:
-
If-Modified-Since
- This request header corresponds to theLast-Modified
response header. Set the value of this to theLast-Modified
value received from the last request to the same endpoint. -
If-None-Match
- This request header corresponds to theEtag
response header. Use theEtag
value received previously for the last request to that endpoint.
Response Cache Headers
An NSHTTPURLResponse
contains a set of HTTP headers, which can include the following directives for how that response should be cached:
-
Cache-Control
- This header must be present in the response from the server to enable HTTP caching by a client. The value of this header may include information like itsmax-age
(how long to cache a response), and whether the response may be cached withpublic
orprivate
access, orno-cache
(not at all). See theCache-Control
section of RFC 2616 for full details.
In addition to Cache-Control
, a server may send additional headers that can be used to conditionally request information as needed (as mentioned in the previous section):
-
Last-Modified
- The value of this header corresponds to the date and time when the requested resource was last changed. For example, if a client requests a timeline of recent photos,/photos/timeline
, theLast-Modified
value could be set to when the most recent photo was taken. -
Etag
- An abbreviation for “entity tag”, this is an identifier that represents the contents requested resource. In practice, anEtag
header value could be something like theMD5
digest of the resource properties. This is particularly useful for dynamically generated resources that may not have an obviousLast-Modified
value.
NSURLConnection Delegate
Once the server response has been received, the NSURLConnection
delegate has an opportunity to specify the cached response in -connection:will
.
NSCached
is a class that contains both an NSURLResponse
with the cached NSData
associated with the response.
In -connection:will
, the cached
object has been automatically created from the result of the URL connection. Because there is no mutable counterpart to NSCached
, in order to change anything about cached
, a new object must be constructed, passing any modified values into –init
, for instance:
// MARK: NSURLConnection Data Delegate
func connection(connection: NSURLConnection!, will Cache Response cached Response: NSCached URLResponse!) -> NSCached URLResponse! {
var mutable User Info = NSMutable Dictionary(dictionary: cached Response.user Info)
var mutable Data = NSMutable Data(data: cached Response.data)
var storage Policy: NSURLCache Storage Policy = .Allowed In Memory Only
…
return NSCached URLResponse(response: cached Response.response, data: mutable Data, user Info: mutable User Info, storage Policy: storage Policy)
}
- (NSCached URLResponse *)connection:(NSURLConnection *)connection
will Cache Response:(NSCached URLResponse *)cached Response
{
NSMutable Dictionary *mutable User Info = [[cached Response user Info] mutable Copy];
NSMutable Data *mutable Data = [[cached Response data] mutable Copy];
NSURLCache Storage Policy storage Policy = NSURLCache Storage Allowed In Memory Only;
…
return [[NSCached URLResponse alloc] init With Response:[cached Response response]
data:mutable Data
user Info:mutable User Info
storage Policy:storage Policy];
}
If -connection:will
returns nil
, the response will not be cached.
func connection(connection: NSURLConnection!, will Cache Response cached Response: NSCached URLResponse!) -> NSCached URLResponse! {
return nil
}
- (NSCached URLResponse *)connection:(NSURLConnection *)connection
will Cache Response:(NSCached URLResponse *)cached Response
{
return nil;
}
When left unimplemented, NSURLConnection
will simply use the cached response that would otherwise be passed into -connection:will
, so unless you need to change or prevent caching, this method does not need to be implemented in the delegate.
Caveats
Just like its unrelated-but-similarly-named cohort, NSCache
, NSURLCache
is not without some peculiarities.
As of iOS 5, disk caching is supported, but only for HTTP, not HTTPS, requests (though iOS 6 added support for this). Peter Steinberger wrote an excellent article on this subject, after digging into the internals while implementing his own NSURLCache subclass.
Another article by Daniel Pasco at Black Pixel describes some unexpected default behavior when communicating with servers that don’t set cache headers.
NSURLCache
reminds us of how important it is to be familiar with the systems we interact with. Chief among them when developing for iOS or OS X is, of course, the URL Loading System.
Untold numbers of developers have hacked together an awkward, fragile system for network caching functionality, all because they weren’t aware that NSURLCache
could be setup in two lines and do it 100× better. Even more developers have never known the benefits of network caching, and never attempted a solution, causing their apps to make untold numbers of unnecessary requests to the server.
So be the change you want to see in the world, and be sure to always start you app on the right foot, by setting a shared NSURLCache
in -application:did
.