All http responses from a server come with the headers that inform our app not to cache the responses:
Cache-Control: no-cache
Pragma: no-cache
Expires: 0
You can implement a custom NSURLCache that only returns cached responses that has not expired.
Example:
#import "CustomURLCache.h"
NSString * const EXPIRES_KEY = @"cache date";
int const CACHE_EXPIRES = -10;
@implementation CustomURLCache
// static method for activating this custom cache
+(void)activate {
CustomURLCache *urlCache = [[CustomURLCache alloc] initWithMemoryCapacity:(2*1024*1024) diskCapacity:(2*1024*1024) diskPath:nil] ;
[NSURLCache setSharedURLCache:urlCache];
}
-(NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
NSCachedURLResponse * cachedResponse = [super cachedResponseForRequest:request];
if (cachedResponse) {
NSDate* cacheDate = [[cachedResponse userInfo] objectForKey:EXPIRES_KEY];
if ([cacheDate timeIntervalSinceNow] < CACHE_EXPIRES) {
[self removeCachedResponseForRequest:request];
cachedResponse = nil;
}
}
return cachedResponse;
}
- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request {
NSMutableDictionary *userInfo = cachedResponse.userInfo ? [cachedResponse.userInfo mutableCopy] : [NSMutableDictionary dictionary];
[userInfo setObject:[NSDate date] forKey:EXPIRES_KEY];
NSCachedURLResponse *newCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:cachedResponse.response data:cachedResponse.data userInfo:userInfo storagePolicy:cachedResponse.storagePolicy];
[super storeCachedResponse:newCachedResponse forRequest:request];
}
@end
If this does not give you enough control then I would implement a custom NSURLProtocol with a startLoading method as below and use it in conjunction with the custom cache.
- (void)startLoading
{
NSMutableURLRequest *newRequest = [self.request mutableCopy];
[NSURLProtocol setProperty:@YES forKey:@"CacheSet" inRequest:newRequest];
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
if (cachedResponse) {
[self connection:nil didReceiveResponse:[cachedResponse response]];
[self connection:nil didReceiveData:[cachedResponse data]];
[self connectionDidFinishLoading:nil];
} else {
_connection = [NSURLConnection connectionWithRequest:newRequest delegate:self];
}
}
Some links:
If somebody would be interested, here is Stephanus answer rewritten in Swift:
class CustomURLCache: NSURLCache {
// UserInfo expires key
let kUrlCacheExpiresKey = "CacheData";
// How long is cache data valid in seconds
let kCacheExpireInterval:NSTimeInterval = 60*60*24*5;
// get cache response for a request
override func cachedResponseForRequest(request:NSURLRequest) -> NSCachedURLResponse? {
// create empty response
var response:NSCachedURLResponse? = nil
// try to get cache response
if let cachedResponse = super.cachedResponseForRequest(request) {
// try to get userInfo
if let userInfo = cachedResponse.userInfo {
// get cache date
if let cacheDate = userInfo[kUrlCacheExpiresKey] as NSDate? {
// check if the cache data are expired
if (cacheDate.timeIntervalSinceNow < -kCacheExpireInterval) {
// remove old cache request
self.removeCachedResponseForRequest(request);
} else {
// the cache request is still valid
response = cachedResponse
}
}
}
}
return response;
}
// store cached response
override func storeCachedResponse(cachedResponse: NSCachedURLResponse, forRequest: NSURLRequest) {
// create userInfo dictionary
var userInfo = NSMutableDictionary()
if let cachedUserInfo = cachedResponse.userInfo {
userInfo = NSMutableDictionary(dictionary:cachedUserInfo)
}
// add current date to the UserInfo
userInfo[kUrlCacheExpiresKey] = NSDate()
// create new cached response
let newCachedResponse = NSCachedURLResponse(response:cachedResponse.response, data:cachedResponse.data, userInfo:userInfo,storagePolicy:cachedResponse.storagePolicy)
super.storeCachedResponse(newCachedResponse, forRequest:forRequest)
}
}
Another possible solution is to modify the response object and clobber the Cache-Control headers from the server and replace them with your own desired values.
There are two places you could do that.
You could do it in a NSURLSessionDataDelegate
in func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse?) -> Void)
, but if you do it there, then can no longer use the usual completion handler-based methods getting results from session tasks.
Another place you could do it is by defining a custom NSURLProtocol
, which intercepts HTTP and HTTPS responses and modifies them.