I'm using AFNetworking and need to cache data in one response for a several minutes. So I set NSUrlCache in app delegate and then in my request setting up it:
NSMutableURLRequest *request = //obtain request;
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
How then set expiration date: if the data was loaded more than n minutes ago, ask response from server and not from disk?
EDIT:
Assume that server doesn't support caching, I need to manage it in code.
So, I found the solution.
The idea is to use connection:willCacheResponse:
method. Before cache the response it will be executed and there we can change response and return new, or return nil and the response will not be cached. As I use AFNetworking, there is a nice method in operation:
- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block;
Add code:
[operation setCacheResponseBlock:^NSCachedURLResponse *(NSURLConnection *connection, NSCachedURLResponse *cachedResponse) {
if([connection currentRequest].cachePolicy == NSURLRequestUseProtocolCachePolicy) {
cachedResponse = [cachedResponse responseWithExpirationDuration:60];
}
return cachedResponse;
}];
Where responseWithExpirationDuration
from category:
@interface NSCachedURLResponse (Expiration)
-(NSCachedURLResponse*)responseWithExpirationDuration:(int)duration;
@end
@implementation NSCachedURLResponse (Expiration)
-(NSCachedURLResponse*)responseWithExpirationDuration:(int)duration {
NSCachedURLResponse* cachedResponse = self;
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)[cachedResponse response];
NSDictionary *headers = [httpResponse allHeaderFields];
NSMutableDictionary* newHeaders = [headers mutableCopy];
newHeaders[@"Cache-Control"] = [NSString stringWithFormat:@"max-age=%i", duration];
[newHeaders removeObjectForKey:@"Expires"];
[newHeaders removeObjectForKey:@"s-maxage"];
NSHTTPURLResponse* newResponse = [[NSHTTPURLResponse alloc] initWithURL:httpResponse.URL
statusCode:httpResponse.statusCode
HTTPVersion:@"HTTP/1.1"
headerFields:newHeaders];
cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:newResponse
data:[cachedResponse.data mutableCopy]
userInfo:newHeaders
storagePolicy:cachedResponse.storagePolicy];
return cachedResponse;
}
@end
So, we set expiration in seconds in http header according to http/1.1
For that we need one of headers to be set up:
Expires, Cache-Control: s-maxage or max-age
Then create new cache response, because the properties is read only, and return new object.
Swift equivalent of @HotJard's solution using URLSession
extension CachedURLResponse {
func response(withExpirationDuration duration: Int) -> CachedURLResponse {
var cachedResponse = self
if let httpResponse = cachedResponse.response as? HTTPURLResponse, var headers = httpResponse.allHeaderFields as? [String : String], let url = httpResponse.url{
headers["Cache-Control"] = "max-age=\(duration)"
headers.removeValue(forKey: "Expires")
headers.removeValue(forKey: "s-maxage")
if let newResponse = HTTPURLResponse(url: url, statusCode: httpResponse.statusCode, httpVersion: "HTTP/1.1", headerFields: headers) {
cachedResponse = CachedURLResponse(response: newResponse, data: cachedResponse.data, userInfo: headers, storagePolicy: cachedResponse.storagePolicy)
}
}
return cachedResponse
}
}
Then implement URLSessionDataDelegate protocol in your custom class
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) {
if dataTask.currentRequest?.cachePolicy == .useProtocolCachePolicy {
let newResponse = proposedResponse.response(withExpirationDuration: 60)
completionHandler(newResponse)
}else {
completionHandler(proposedResponse)
}
}
Don't forget to create your configuration and session, passing in the your custom class as the delegate reference e.g.
let session = URLSession(
configuration: URLSession.shared.configuration,
delegate: *delegateReference*,
delegateQueue: URLSession.shared.delegateQueue
)
let task = session.dataTask(with: request)
task.resume()
The expiration of responses in the NSURLCache
is controlled via the Cache-Control
header in the HTTP response.
EDIT I see you've updated your question. If the server doesn't provide the Cache-Control header in the response, it won't be cached. Every request to that endpoint will load the endpoint rather than return a cached response.