NSURLCache - Кэширование диска для запроса GET с неработающими параметрами

Я пытаюсь кэшировать некоторые HTTP-запросы на диск с помощью NSURLCache. Проведя несколько тестов с AFNetworking и не заработав, я создал простой пример, используя NSURLRequest.

Я инициализирую кеш в AppDelegate следующим образом, устанавливая размер памяти равным 0, заставляя его всегда идти на диск:

NSUInteger sz = 1024*1024*20;
NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:sz diskPath:nil];
[NSURLCache setSharedURLCache:cache];

Я использую следующий код для хранения ответа:

NSURLCache *urlCache = [NSURLCache sharedURLCache];

NSString *urlString = @"http://localhost:8000/cities/api/cities/?lang=en";
NSHTTPURLResponse *response = nil;
NSError *error = nil;

NSURL *finalUrl = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:finalUrl];
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;

NSData *data = nil;

data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed];
[urlCache storeCachedResponse:cachedResponse forRequest:request];

В моем запросе есть HTTP-заголовок Cache-Control, например:

response['cache-control'] = 'max-age=36000, public'

Я вижу, что ответ кэшируется в файле Cache.db.

И позже, в следующем исполнении или даже в том же самом, я использую следующий код, чтобы попытаться получить ответ от Cache:

NSCachedURLResponse *cachedResponse = [urlCache cachedResponseForRequest:request];
response = cachedResponse.response;
data = cachedResponse.data;

Проблема в том, что cachedResponse всегда имеет значение null, когда в запросе есть параметры GET. Если запрос "http://localhost:8000/cities/api/cities/ ", результат сохраняется и восстанавливается позже. Но когда у него есть параметры GET, что-то мешает методу [NSURLCache cachedResponseForRequest:request] найти запрос.

Учитывая это, я создал подкласс NSURLCache и реализовал этот метод следующим образом:

- (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path {
self = [super initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path];
if (self) {
    NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *dir = (NSString*)[docPaths objectAtIndex:0];
    dir = [dir stringByAppendingString:@"/uk.co.airsource.TestURLCache/nsurlcache"];
    NSString *path = [dir stringByAppendingFormat:@"/Cache.db"];
    self.db = [[FMDatabase alloc] initWithPath:path];
    [self.db open];
    int o = 4;
}
return self;

}

- (NSCachedURLResponse *) cachedResponseForRequest:(NSURLRequest *)request {
NSCachedURLResponse *cachedURLResponse = [super cachedResponseForRequest:request];
if (cachedURLResponse == nil && [request.HTTPMethod isEqualToString:@"GET"]) {
    //[db executeQuery:@"select * from test where a = ?", @"hi'",  nil];
    NSLog(@"%@", request.URL.absoluteString);
    FMResultSet *cfurl_cache_response = [self.db executeQuery:@"select * from cfurl_cache_response where request_key = ? limit 1", request.URL.absoluteString, nil];
    if ([cfurl_cache_response next]) {
        id entry_ID = [cfurl_cache_response objectForColumnName:@"entry_ID"];
        [cfurl_cache_response close];
        if (entry_ID != [NSNull null]) {
            FMResultSet *cfurl_cache_blob_data = [self.db executeQuery:@"select * from cfurl_cache_blob_data where entry_ID = ? limit 1", entry_ID, nil];
            if ([cfurl_cache_blob_data next]) {
                id response_object = [cfurl_cache_blob_data objectForColumnName:@"response_object"];
                [cfurl_cache_blob_data close];
                FMResultSet *cfurl_receiver_data = [self.db executeQuery:@"select * from cfurl_cache_receiver_data where entry_ID = ? limit 1", entry_ID, nil];
                if ([cfurl_receiver_data next]) {
                    id receiver_data = [cfurl_receiver_data objectForColumnName:@"receiver_data"];
                    [cfurl_receiver_data close];
                    if (response_object != [NSNull null] && receiver_data != [NSNull null] && response_object && receiver_data) {
                        NSURLResponse *urlResponse = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:[[request allHTTPHeaderFields] objectForKey:@"Accept"] expectedContentLength:[(NSData *)response_object length] textEncodingName:nil];
                        cachedURLResponse = [[NSCachedURLResponse alloc] initWithResponse:urlResponse data:receiver_data userInfo:nil storagePolicy:NSURLCacheStorageAllowed];
                    }
                }
            }
        }
    }
}
return cachedURLResponse;

}

В этом случае в запросе GET с параметрами он следует за каждой отдельной строкой, пока не получит NSURLResponse, который был ранее кэширован.

Любые идеи о том, почему он не работает нормально, когда запрос имеет параметры GET?

БД с двумя сохраненными запросами


person jcardenete    schedule 11.02.2015    source источник


Ответы (3)


Проблема оказалась с не указанием объема памяти, если указать объем памяти следующим образом,

Здесь я указываю объем памяти равным 1 МБ.

[[NSURLCache alloc] initWithMemoryCapacity:1*1024*1024 diskCapacity:sz diskPath:nil];

это будет работать, мне кажется, что объем памяти нужен независимо от того, насколько мал объем, но не может быть пустым. Затем вы сможете извлечь сохраненный кэш следующим образом:

NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
response = cachedResponse.response;
data = cachedResponse.data;
person ldindu    schedule 11.02.2015
comment
Я перезапустил все и сделал, как вы сказали, но происходит то же самое. Ответ сохраняется в базе данных. Затем я закрываю приложение и снова открываю его, и когда я пытаюсь получить ответ из кеша, он возвращает nil. - person jcardenete; 12.02.2015
comment
Мне кажется, что вообще не собирается проверять кэш диска в Cache.db - person jcardenete; 12.02.2015
comment
можете ли вы подтвердить, возвращает ли ваш локальный URL-адрес контент? - person ldindu; 12.02.2015
comment
Мне показалось, что ваш локальный сервер не устанавливает заголовки кеша? Если это так, то вам нужно указать его на стороне клиента. - person ldindu; 12.02.2015
comment
Вы можете подтвердить это, изменив URL-адрес на google, www.google.com в качестве заголовков кеша google. - person ldindu; 13.02.2015
comment
ldindu, я обновил описание ошибки, обнаружив, что проблема возникает только тогда, когда в запросе есть параметры GET. - person jcardenete; 13.02.2015

Наконец, я переопределил NSURLCache, чтобы получить то, что искал:

  1. Запросы GET (с параметрами), кэшируемые на диске
  2. Запросы помещаются в кеш, пока значение «Cache-Control max-age» говорит, что срок действия не истек, и снова отправляются на сервер после истечения срока действия, сохраняя новый ответ и начиная время истечения срока действия.
  3. В автономном режиме всегда возвращайте ответ (если он существует) из кеша

NTURLCache — переопределяет NSURLCache

Я серьезно думаю, что это проблема, которая не работает должным образом в iOS 8.1.

person jcardenete    schedule 03.03.2015
comment
Я решил это, создав подкласс NSURLCache, вы можете найти проект по ссылке в моем ответе. - person jcardenete; 20.04.2015

Проблема сохраняется и для iOS 11.x, хотя у меня есть iPad Mini с версией iOS 9.3.5, у которой отлично работает кеш. Что-то определенно не так с NSURLCache/URLCache

person Sameer Bhide    schedule 31.08.2018