Как использовать NSURLCache для кэширования контента, обслуживаемого NSURLProtocol

Я написал NSURLProtocol, который будет проверять исходящие http запросы по plist URL-адресам для сопоставления локальных путей и вместо этого обслуживать локальный контент, а затем кэшировать его с помощью NSURLCache:

- (void)startLoading
{   
    //Could this be why my responses never come out of the cache?
    NSURLResponse *response =[[NSURLResponse alloc]initWithURL:self.request.URL
                                                      MIMEType:nil expectedContentLength:-1
                                              textEncodingName:nil];

    //Get the locally stored data for this request
    NSData* data = [[ELALocalPathSubstitutionService singleton] getLocallyStoredDataForRequest:self.request];

    //Tell the connection to cache the response
    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];

    //Have the connection load the data we just fetched
    [[self client] URLProtocol:self didLoadData:data];

    //Tell the connection to finish up
    [[self client] URLProtocolDidFinishLoading:self];
}

Я ограничиваю количество извлечений локальных данных одним. Предполагается, что в первый раз, когда он будет получен, он будет получен из NSBundle, но после этого он будет использовать запас NSURLCache, чтобы проверить, должен ли он поступать из кэша или из сети< /сильный>:

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    //Check if we have pre-loaded data for that request
    ELAPathSubstitution* pathSub = [[ELALocalPathSubstitutionService singleton] pathSubForRequest:request];

    //We don't have a mapping for this URL
    if (!pathSub)
        return NO;

    //If it's been fetched too many times, don't handle it
    if ([pathSub.timesLocalDataFetched intValue] > 0)
    {
        //Record that we refused it.
        [pathSub addHistoryItem:ELAPathSubstitutionHistoryRefusedByProtocol];
        return NO;
    }

    //Record that we handled it.
    [pathSub addHistoryItem:ELAPathSubstitutionHistoryHandledByProtocol];
    return YES;
}

К сожалению, кажется, что локальные данные попадут в кеш, но никогда не вернутся обратно. Вот фрагмент журнала:

History of [https://example.com/image.png]:
[2014-04-29 18:01:53 +0000] = [ELAPathSubstitutionHistoryHandledByProtocol]
[2014-04-29 18:01:53 +0000] = [ELAPathSubstitutionHistoryHandledByProtocol]
[2014-04-29 18:01:53 +0000] = [ELAPathSubstitutionHistoryHandledByProtocol]
[2014-04-29 18:01:53 +0000] = [ELAPathSubstitutionHistoryCacheMiss]
[2014-04-29 18:01:53 +0000] = [ELAPathSubstitutionHistoryDataFetched]
[2014-04-29 18:01:53 +0000] = [ELAPathSubstitutionHistoryAddedToCache]
[2014-04-29 18:02:11 +0000] = [ELAPathSubstitutionHistoryRefusedByProtocol]
[2014-04-29 18:02:11 +0000] = [ELAPathSubstitutionHistoryRefusedByProtocol]

[2014-04-29 18:02:11 +0000] = [ELAPathSubstitutionHistoryCacheMiss] 
[2014-04-29 18:02:11 +0000] = [ELAPathSubstitutionHistoryAddedToCache]
[2014-04-29 18:02:50 +0000] = [ELAPathSubstitutionHistoryRefusedByProtocol]
[2014-04-29 18:02:50 +0000] = [ELAPathSubstitutionHistoryCacheHit]

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

Я боюсь, что мой подкласс NSURLProtocol строит свои ответы таким образом, что позволяет кэшировать их, но предотвращает их извлечение из кэша. Любые идеи?

Заранее спасибо. :)


person Scott McCoy    schedule 29.04.2014    source источник


Ответы (3)


За взаимодействие с кэшем системы загрузки URL отвечает объект NSURLProtocolClient, который действует как клиент NSURLProtocol. Если запрос использует NSURLRequestUseProtocolCachePolicy в качестве политики кэширования, реализация протокола должна применить правильные правила, специфичные для протокола, чтобы определить, следует ли кэшировать ответ или нет.

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

Однако, поскольку клиент, который система предоставляет нам, является частным и непрозрачным, вы можете взять дело в свои руки и взаимодействовать с системным кешем в рамках вашего протокола. Если вы хотите пойти по этому пути, вы можете напрямую использовать NSURLCache. Первый шаг — переопределить -cachedResponse в вашем протоколе. Если вы внимательно прочитаете документацию, реализация по умолчанию устанавливает это только из значения, переданного в инициализатор. Переопределите его, чтобы он обращался к общий кэш URL (или ваш собственный кэш URL):

- (NSCachedURLResponse *) cachedResponse {
    return [[NSURLCache sharedURLCache] cachedResponseForRequest:[self request]];
}

Теперь в тех местах, где вы обычно вызываете cachedResponseIsValid: на клиенте, также сохраните NSCachedURLResponse в файле NSURLCache. Например, когда у вас есть полный набор байтов и ответ:

[[NSURLCache sharedURLCache] storeCachedResponse:cachedResponse forRequest:[self request]];
person quellish    schedule 07.05.2014

Спасибо за информацию, ребята, но, как и Скотт, я все еще вижу проблемы с кэшированием, и я не понимаю, на ком лежит конкретная ответственность.

Я уверен, что ответы моего пользовательского NSURLProtocol кэшируются. Я уведомляю клиент протокола следующим образом: [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed]; после этого я могу вручную запросить NSURLCache, и это вернет соответствующий NSCachedURLResponse.

Это при последующих запросах, где в кеше есть запрос, о котором я не знаю. Я бы предположил, что для предыдущего запроса, в котором мне был предоставлен etag, клиент NSURLProtocol установил бы заголовок if-none-match для запроса, поступающего в мой протокол?

Должен ли я сам реализовывать эту сторону HTTP-кэширования в своем пользовательском протоколе?

person biddster    schedule 05.10.2015

Я решил эту проблему, используя собственный NSUrlCache. Я думаю, что его проще использовать, чем NSurlProtocol. Я использовал код в приложении, которое доступно в магазине приложений. Код для этого опубликован на github по адресу https://github.com/evermeer/EVURLCache.

person Edwin Vermeer    schedule 03.05.2014
comment
Это не отвечает на мой вопрос - мне нужно знать, в частности, как заставить NSURLCache кэшировать ответ, сгенерированный NSURLProtocol. - person Scott McCoy; 05.05.2014