Как установить срок действия кеша NSURLRequest?

Я использую AFNetworking и вам нужно кэшировать данные в одном ответе в течение нескольких минут. Поэтому я устанавливаю NSUrlCache в делегат приложения, а затем в моем запросе настраиваю его:

NSMutableURLRequest *request = //obtain request; 
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;

Как установить дату истечения срока действия: если данные были загружены более n минут назад, спросите ответ от сервера, а не с диска?

UPD: Предположим, что сервер не поддерживает кеширование, мне нужно управлять им в коде.

Ответ 1

Итак, я нашел решение.

Идея заключается в использовании метода connection:willCacheResponse:. Перед кэшем ответ будет выполнен, и там мы сможем изменить ответ и вернуть новый, или вернуть нуль, и ответ не будет кэшироваться. Поскольку я использую AFNetworking, есть хороший метод в работе:

- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block;

Добавить код:

  [operation setCacheResponseBlock:^NSCachedURLResponse *(NSURLConnection *connection, NSCachedURLResponse *cachedResponse) {
    if([connection currentRequest].cachePolicy == NSURLRequestUseProtocolCachePolicy) {
      cachedResponse = [cachedResponse responseWithExpirationDuration:60];
    }
    return cachedResponse;
  }];

Где responseWithExpirationDuration из категории:

@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

Итак, мы устанавливаем истечение в секундах в заголовке http в соответствии с http/1.1 Для этого нам нужен один из настраиваемых заголовков: Истекает, Cache-Control: s-maxage или max-age Затем создайте новый ответ на кеш, потому что свойства доступны только для чтения и возвращают новый объект.

Ответ 2

Быстрый эквивалент решения @HotJard с использованием 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
    }
}

Затем реализуем протокол URLSessionDataDelegate в вашем пользовательском классе

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)
    }
}

Не забудьте создать конфигурацию и сеанс, передав свой собственный класс в качестве ссылки на делегат, например.

let session = URLSession(
        configuration: URLSession.shared.configuration,
        delegate: *delegateReference*,
        delegateQueue: URLSession.shared.delegateQueue
    )
let task = session.dataTask(with: request)
task.resume()

Ответ 3

Истечение ответов в NSURLCache контролируется через заголовок Cache-Control в ответе HTTP.

EDIT Я вижу, что вы обновили свой вопрос. Если сервер не предоставляет заголовок Cache-Control в ответе, он не будет кэшироваться. Каждый запрос к этой конечной точке будет загружать конечную точку, а не возвращать кешированный ответ.