How to set NSURLRequest cache expiration?
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.1For that we need one of headers to be set up:Expires, Cache-Control: s-maxage or max-ageThen 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.