How to Use Nsurlsession to Determine If Resource Has Changed

How to use NSURLSession to determine if resource has changed?

I created a HTTPEntityFingerprint structure which stores some of the entity headers: Content-MD5, Etag, and Last-Modified.

import Foundation

struct HTTPEntityFingerprint {
let contentMD5 : String?
let etag : String?
let lastModified : String?
}

extension HTTPEntityFingerprint {
init?(response : NSURLResponse) {
if let httpResponse = response as? NSHTTPURLResponse {
let h = httpResponse.allHeaderFields
contentMD5 = h["Content-MD5"] as? String
etag = h["Etag"] as? String
lastModified = h["Last-Modified"] as? String

if contentMD5 == nil && etag == nil && lastModified == nil {
return nil
}
} else {
return nil
}
}

static func match(first : HTTPEntityFingerprint?, second : HTTPEntityFingerprint?) -> Bool {
if let a = first, b = second {
if let md5A = a.contentMD5, md5B = b.contentMD5 {
return md5A == md5B
}
if let etagA = a.etag, etagB = b.etag {
return etagA == etagB
}
if let lastA = a.lastModified, lastB = b.lastModified {
return lastA == lastB
}
}

return false
}
}

When I get an NSHTTPURLResponse from an NSURLSession, I create an HTTPEntityFingerprint from it and compare it against a previously stored fingerprint using HTTPEntityFingerprint.match. If the fingerprints match, then the HTTP resource hasn't changed and thus I do not need to deserialized the JSON response again; however, if the fingerprints do not match, then I deserialize the JSON response and save the new fingerprint.

This mechanism only works if your server returns at least one of the 3 entity headers: Content-MD5, Etag, or Last-Modified.

More Details on NSURLSession and NSURLCache Behavior

The caching provided by NSURLSession via NSURLCache is transparent, meaning when you request a previously cached resource NSURLSession will call the completion handlers/delegates as if a 200 response occurred.

If the cached response has expired then NSURLSession will send a new request to the origin server, but will include the If-Modified-Since and If-None-Match headers using the Last-Modified and Etag entity headers in the cached (though expired) result; this behavior is built in, you don't have to do anything besides enable caching. If the origin server returns a 304 (Not Modified), then NSURLSession will transform this to a 200 response the application (making it look like you fetched a new copy of the resource, even though it was still served from the cache).

How to know if NSURLSessionDataTask response came from cache?

Two easy options come to mind:

  • Call [[NSURLCache sharedURLCache] cachedResponseForRequest:request] before you make the request, store the cached response, then do that again after you finish receiving data, and compare the two cached responses to see if they are the same.
  • Make an initial request with the NSURLRequestReturnCacheDataDontLoad cache policy, and if that fails, make a second request with a more sane policy.

The first approach is usually preferable, as the second approach will return data that exists in the cache even if it is stale. However, in some rare cases (e.g. an offline mode), that might be what you want, which is why I mentioned it.

NSURLSession not using cached responses

I can't tell you with absolute certainty why the cache isn't being consulted, but I can give you a list of the most likely reasons:

  • The server did not respond with 304 when queried about the validity of that ETag header (IIRC using a HEAD request).
  • The request is too big—either relative to the size of the buffer or in absolute terms. The cache should at least be a couple of orders of magnitude bigger than the requests that you would typically cache; anything over about 5% of the cache size will not be cached.
  • The request method is something other than GET. (Only GET requests are cached unless you monkey with the machinery significantly.)
  • More than 10 minutes have elapsed (600 seconds isn't very long).
  • The request was made in a different URL session that has a different backing cache.
  • The request was made in an ephemeral URL session or a session that for some other reason has no cache.
  • The session actually is returning the cached response, but you're seeing a request because it is revalidating a little more aggressively than you might expect—possibly because it will reach its maximum age so soon.
  • Your URL request is getting handled in the background by a custom NSURLProtocol that doesn't respect the cache (e.g. because of some badly behaved third-party networking or advertising framework).
  • The request had not actually been fully written to the cache when you tried to retrieve it (timing race caused by multiple threads).

I'm probably forgetting several others. With that said, if I'm forgetting them, that probably means that they aren't documented.

So...

If you verify that everything listed above is working as expected, file a bug at bugreporter.apple.com and include enough code to reproduce the problem, along with a packet dump if possible.

How to use NSURLCache with My Web service calls using NSURLSession

NSURLSession uses a cache by default, based on the standard protocol caching policy, which basically means that if the cache control headers allow it to be cached, if it hasn't expired, if it is a GET request, if the request is less than about 5% of the size of the cache, etc., it will cache it, and will use the cache when you request it again.

However, it sounds like you also want to be able to clear the cache under programmatic control. To do that, first set the URLCache property on the session configuration to a newly allocated NSURLCache before you create the session.

Later, when you want to clear the cache, just call the removeAllCachedResponses method on that cache object.

Of course, the question is how you'll know if something has changed on the server, if you're caching all your requests. Chances are, you'll need to make at least some requests with caching disabled. The details are, of course, specific to what you're trying to do.

NSURLCache for NSURLSession background tasks

Wow i asked this question a long time ago, and still no answers.

Tell you the truth, i ended up using a DTS token for this one.

Turns out that NSURLSession background configuration don't support cashing at all, no matter what task you use.

Its a design decision by apple that's badly documented.

Heres part of the DTS replay that i received:

I’m responding to your question about the relationship between NSURLSession background sessions and NSURLCache. You wrote:

Then i add a NSURLCache object to my configuration the NSURLSession mechanism disregards it completely.


Indeed. That is expected behavior, as things currently stand NSURLSession background sessions /never/ cache. There’s a bunch of reasons for this:

  • It’s infeasible to fully support the URLCache property. Specifically, we can’t allow you to use a custom subclass of NSURLCache because that would require us to load your code into a system daemon (remember that the actual work here is done by the NSURLSession daemon while your app is suspended, or perhaps even terminated).

  • Even if we limited you to just instances of the standard NSURLCache class, things still get tricky because we would want the disk space used by that cache to be charged to your app. That’s hard to orchestrate given that security barriers between your app and the NSURLSession daemon.

  • There’s a philosophical disconnect here. NSURLSession background sessions were intended to be used for a small number of large transfers, while NSURLCache is only really useful when you’re doing a large number of small transfers.


Note: Running lots of small transfers through an NSURLSession background session is likely to cause problems for other reasons. For more info on this, read the following DevForums posts:

  • NSURLSession’s Resume Rate Limiter

https://forums.developer.apple.com/message/42352#42352

  • Moving to Fewer, Larger Transfers

https://forums.developer.apple.com/thread/14853

If you’d like the NSURLSession engineers to reconsider this caching design decision, you should feel free to file a bug requested that this be supported. Make sure you explain your use case in detail.

https://developer.apple.com/bug-reporting/

Your bug may be returned as ‘behaves correctly’, but it doesn’t hurt to ask.

On the subject of bugs, I presume you looked through the NSURLSession documentation and didn’t find this gotcha documented. If so, I’d appreciate you filing a bug against the documentation that you looked at requesting that it include information about this issue.



Related Topics



Leave a reply



Submit