37

我正在使用AFNetworkingandSDURLCache进行所有的网络操作。

我是这样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,根据苹果文档,它的工作方式如下:

如果请求的 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

4

3 回答 3

12

好吧,我终于找到了一个不那么难看的解决方法:

第一的

如果您使用的是 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 何时不可用,并指定始终使用缓存的请求,无论如何。(离线模式)

笔记

  • 我仍然不知道当应用程序启动后立即发出请求而 AF 尚未获得 Internet 状态时会发生这种情况networkReachabilityStatusAFNetworkReachabilityStatusUnknown

  • 请记住,为了使其正常工作,服务器必须在 http 响应中设置正确的缓存标头。

更新

貌似IOS6在无网情况下加载缓存响应有些问题,所以即使请求被缓存了,请求缓存策略设置NSURLRequestReturnCacheDataDontLoad了请求也会失败。

因此,一个丑陋的解决方法是修改(void)connection:(NSURLConnection __unused *)connection didFailWithError:(NSError *)errorinAFURLConnectionOperation.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's 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;
        }
    }
}
于 2013-04-08T17:30:07.037 回答
0

没有您的 HTTP 标头就无法说明太多——但最常见的原因是NSURLProtocol在向 WebView 提供缓存响应之前强制重新验证。

请看这里: http ://robnapier.net/blog/offline-uiwebview-nsurlprotocol-588

于 2012-06-17T15:43:07.843 回答
0

听起来您希望请求成功,即使缓存说数据已过期并且应该从服务器检索。您可能有一些运气设置某些请求的缓存策略(在线与离线的不同策略),您宁愿使用陈旧数据而不是失败。

NSMutableURLRequest -> setCachePolicy

看起来NSURLRequestReturnCacheDataDontLoad是您想要的离线模式策略。

希望有帮助!

于 2013-01-20T16:05:11.270 回答