Подтвердить что ты не робот

SDURLCache с AFNetworking и автономным режимом не работает

Я использую AFNetworking и SDURLCache для всех своих сетевых операций.

У меня SDURLCache установлено следующее:

SDURLCache *urlCache = [[SDURLCache alloc]
        initWithMemoryCapacity:1024*1024*2   // 2MB mem cache
        diskCapacity:1024*1024*15 // 15MB disk cache
        diskPath:[SDURLCache defaultCachePath]];
    [urlCache setMinCacheInterval:1];
    [NSURLCache setSharedURLCache:urlCache];

Весь мой запрос использует cachePolicy NSURLRequestUseProtocolCachePolicy, который, согласно документам apple, работает следующим образом:

Если для запроса не существует NSCachedURLResponse, тогда данные извлекаются из исходного источника. Если есть кешированный ответ для запроса, система загрузки URL-адресов проверяет ответ чтобы определить, указывает ли он, что содержимое должно быть пересмотрено. Если содержимое должно быть подтверждено, соединение выполнено с чтобы узнать, изменилась ли она. Если он не изменился, то ответ возвращается из локального кеша. Если он изменился, данные извлекаются из исходного источника.

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

Таким образом, все работает отлично даже в режиме самолета, пока кеш не устарел. Когда кеш истекает (max-age и другие), вызывается блок отказа.

Я немного искал внутри SDURLCache, и этот метод возвращает ответ с достоверными данными (я проанализировал данные в строке и содержал кешированную информацию)

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
    request = [SDURLCache canonicalRequestForRequest:request];

    NSCachedURLResponse *memoryResponse =
        [super cachedResponseForRequest:request];
    if (memoryResponse) {
        return memoryResponse;
    }

    NSString *cacheKey = [SDURLCache cacheKeyForURL:request.URL];

    // NOTE: We don't handle expiration here as even staled cache data is
    // necessary for NSURLConnection to handle cache revalidation.
    // Staled cache data is also needed for cachePolicies which force the
    // use of the cache.
    __block NSCachedURLResponse *response = nil;
    dispatch_sync(get_disk_cache_queue(), ^{
        NSMutableDictionary *accesses = [self.diskCacheInfo
            objectForKey:kAFURLCacheInfoAccessesKey];
        // OPTI: Check for cache-hit in in-memory dictionary before to hit FS
        if ([accesses objectForKey:cacheKey]) {
            response = [NSKeyedUnarchiver unarchiveObjectWithFile:
                [_diskCachePath stringByAppendingPathComponent:cacheKey]];
            if (response) {
                // OPTI: Log entry last access time for LRU cache eviction
                // algorithm but don't save the dictionary
                // on disk now in order to save IO and time
                [accesses setObject:[NSDate date] forKey:cacheKey];
                _diskCacheInfoDirty = YES;
            }
        }
    });

    // OPTI: Store the response to memory cache for potential future requests
    if (response) {
        [super storeCachedResponse:response forRequest:request];
    }

    return response;
}

Итак, на данный момент я понятия не имею, что делать, потому что я считаю, что ответ обрабатывается ОС, а затем AFNetworking получает

- (void)connection:(NSURLConnection *)__unused connection 
  didFailWithError:(NSError *)error

внутри AFURLConnectionOperation.

4b9b3361

Ответ 1

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

Первая

Если вы используете IOS5/IOS6, вы можете удалить SDURLCache и использовать собственный:

//Set Cache
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
                                                     diskCapacity:20 * 1024 * 1024
                                                         diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];

Но помните, что в IOS5 запросы https не будут кэшироваться в IOS6, они будут.

Второй

Нам нужно добавить следующие рамки для нашего Prefix.pch, чтобы AFNetworking могла начать мониторинг нашего интернет-соединения.

#import <MobileCoreServices/MobileCoreServices.h>
#import <SystemConfiguration/SystemConfiguration.h>

Третий

Нам нужен экземпляр AFHTTPClient, чтобы мы могли перехватить каждый исходящий запрос и изменить его cachePolicy

-(NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters {

    NSMutableURLRequest * request = [super requestWithMethod:method path:path parameters:parameters];
    if (request.cachePolicy == NSURLRequestUseProtocolCachePolicy && self.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {
        request.cachePolicy = NSURLRequestReturnCacheDataDontLoad;
    }

    if (self.networkReachabilityStatus == AFNetworkReachabilityStatusUnknown) {

        puts("uknown reachability status");
    }

    return request;
}

С помощью этих кодов кода теперь мы можем обнаружить, когда wifi/3g недоступен, и указать, что запрос всегда будет использовать кеш, несмотря ни на что. (Автономный режим)

Примечания

  • Я все еще не знаю, что делать, если networkReachabilityStatus is AFNetworkReachabilityStatusUnknown Это может случиться, что запрос выполняется, как только приложение запускается, и AF еще не получил статус Интернета.

  • Помните, что для этого сервер должен установить правильные заголовки кеша в ответе http.

UPDATE

Похоже, что у IOS6 возникают проблемы с загрузкой кэшированных ответов в ситуациях без Интернета, поэтому даже если запрос кэшируется и политика кэша запросов установлена ​​на NSURLRequestReturnCacheDataDontLoad, запрос будет терпеть неудачу.

Таким образом, уродливое обходное решение заключается в изменении (void)connection:(NSURLConnection __unused *)connection didFailWithError:(NSError *)error в AFURLConnectionOperation.m для получения кэшированного ответа, если запрос завершился неудачно, но только для определенных политик кэша.

- (void)connection:(NSURLConnection __unused *)connection
  didFailWithError:(NSError *)error
{
    self.error = error;

    [self.outputStream close];

    [self finish];

    self.connection = nil;

    //Ugly hack for making the request succeed if we can find a valid non-empty cached request
    //This is because IOS6 is not handling cache responses right when we are in a no-connection sittuation
    //Only use this code for cache policies that are supposed to listen to cache regarding it expiration date
    if (self.request.cachePolicy == NSURLRequestUseProtocolCachePolicy ||
        self.request.cachePolicy == NSURLRequestReturnCacheDataElseLoad ||
        self.request.cachePolicy == NSURLRequestReturnCacheDataDontLoad) {

        NSCachedURLResponse * cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
        if (cachedResponse.data.length > 0) {
            self.responseData = cachedResponse.data;
            self.response = cachedResponse.response;
            self.error = nil;
        }
    }
}

Ответ 2

Невозможно многое сказать без ваших HTTP-заголовков, но наиболее распространенной причиной этого является NSURLProtocol принудительная повторная аттестация перед доставкой кэшированного ответа на WebView.

Пожалуйста, смотрите здесь: http://robnapier.net/blog/offline-uiwebview-nsurlprotocol-588

Ответ 3

Похоже, вы хотите, чтобы запрос был успешным, хотя кеш говорит, что данные истекли и должны быть получены с сервера. Возможно, вам повезло установить политику кэширования (различные политики для онлайн-офф-офф) определенных запросов, где вы предпочитаете использовать устаревшие данные, чем сбой.

NSMutableURLRequest → setCachePolicy

Похоже, что NSURLRequestReturnCacheDataDontLoad - это политика, которую вы хотите для автономного режима.

Надеюсь, что это поможет!