Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set NSURLRequest cache expiration?

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.

like image 457
HotJard Avatar asked Nov 08 '13 09:11

HotJard


People also ask

How do I set the expiry for cache?

One of the way to Set cache expiration is by using . htaccess file. Below code will set expiration for it's respective file type, e.g. for CSS files expiration will be 14 days.

What is cache expire time?

The value of the Expires date/time can cause the following specific cache behavior: When the Expires date is equal to the Date header value, the response is considered to be expired. When a response has an Expires header field with a date/time that is in the future, then that response is considered "cacheable".


2 Answers

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.

like image 134
HotJard Avatar answered Oct 18 '22 23:10

HotJard


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()
like image 27
Anwuna Avatar answered Oct 18 '22 21:10

Anwuna