AFHTTPSessionManager 上的 setDataTaskWillCacheResponseBlock 设置的块永远不会被调用

Posted

技术标签:

【中文标题】AFHTTPSessionManager 上的 setDataTaskWillCacheResponseBlock 设置的块永远不会被调用【英文标题】:Block set by setDataTaskWillCacheResponseBlock on AFHTTPSessionManager is never called 【发布时间】:2015-06-11 13:01:19 【问题描述】:

我开发了一个应用程序,它严重依赖 AFNetworking 将数据从私有 API 加载到 Mantle 模型中。我的 API 客户端是 AFHTTPSessionManager 的单例子类。

我正在尝试通过利用NSURLCache 来实现基本的离线模式。方法是如果AFNetworkReachabilityManager表示网络不可达,则将dataTaskWithRequest:request:completionHandler上的请求缓存策略修改为NSURLRequestReturnCacheDataDontLoad

虽然我可以通过在测试期间使用断点来验证这确实有效,但请求本身并没有被缓存。我通过从 Xcode 下载应用程序容器并检查 Cache.db sqlite 文件来验证这一点,该文件是空的。我还通过手动创建一个虚拟NSCachedURLResponse 并使用storeCachedResponse:forRequest 将其强制存储在缓存中来验证我正在查看正确的缓存。然后虚拟响应确实出现在 Cache.db sqlite 文件中。

所以,我认为我将问题缩小到我的“通常”响应没有被缓存。我还可以修改服务器 API 发送的标头。我打算在这里做的是在 API 响应上设置一个标头 Cache-Control: private(以免使其他使用此 API 缓存响应的客户端不应该使用),并在 setDataTaskWillCacheResponseBlock 块中修改此缓存标头,但这个块永远不会触发。

我了解 URL 系统可能会根据一些未记录的规则(主要是 Cache-Control 标头的存在以及响应大小/缓存大小比率)来决定何时调用调用块的 URLSession:dataTask:willCacheResponse:completionHandler。但是我的响应是一个 145 字节的 JSON,并且设置了 Cache-Control 标头(我通过 curl -v 验证并检查了我的请求的 success 块的 task 参数。

我还尝试了更激进的缓存标头Cache-Control: public, max-age=2592000,以查看它是否缓存响应或至少调用块,但行为完全相同。我还检查了myAFHTTPSessionManagerSubclass.session.delegate 属性,正如预期的那样,它确实指向myAFHTTPSessionManagerSubclass

我也尝试在我的子类中直接覆盖URLSession:dataTask:willCacheResponse:completionHandler,但它仍然没有被调用。在 Pods 目录中的 AFURLSessionManager.m 上的同一委托方法上设置断点也不起作用,执行永远不会在断点处停止。

根据我的Podfile.lock 文件,我使用的是 AFNetworking 2.5.4 版。

那么,如何让我的响应缓存(最好不要对响应设置积极的缓存策略),以便我可以在我的应用中实现快速离线模式?

编辑:我还尝试创建一个NSURLSession 看看这是否可行。所以,我创建了一个简单的 Node.js 服务器,它只回答一些简单的问题并设置一个缓存头:

$ curl -v http://192.168.1.107:1337/
* Hostname was NOT found in DNS cache
*   Trying 192.168.1.107...
* Connected to 192.168.1.107 (192.168.1.107) port 1337 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: 192.168.1.107:1337
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Cache-Control: public
< Date: Thu, 11 Jun 2015 14:38:14 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
Hello World

然后创建一个简单的视图控制器进行测试:

- (void)viewDidLoad 
    [super viewDidLoad];

    // Prime the cache
    [NSURLCache setSharedURLCache:[[NSURLCache alloc] initWithMemoryCapacity:2*1024 diskCapacity:10*1024*1024 diskPath:@"mytestcache"]];
    // The sleep is to be absolutely sure the cache did initialise
    sleep(2);

    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
    [[session dataTaskWithURL:[NSURL URLWithString:@"http://192.168.1.107:1337"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
        NSLog(@"Got response: %@", [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
    ] resume];


-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *))completionHandler 

    completionHandler(proposedResponse);

响应已正确打印,但从未调用过willCacheResponse(我设置了断点)。我尝试再次检查容器,并创建了mytestcache 目录,但它是空的。我也试过Cache-Control: max-age=86400,得到同样的结果。

(交叉发布到 AFNetworking 问题:https://github.com/AFNetworking/AFNetworking/issues/2780)

【问题讨论】:

【参考方案1】:

好吧,我会让你大吃一惊 - 你上面写的代码工作得很好,......但它不适合你想要实现的目标 ;)

原因很简单 - 当您通过方法 dataTaskWithRequest:completionHandler: 创建请求时,没有调用委托方法。您需要改用 dataTaskWithRequest: 方法。

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html#//apple_ref/doc/uid/TP40013509-SW1 - 先读注意

第二件事是,当我们谈论缓存时,NSURLSession 真的很糟糕。在 iOS 7.x/8.x 中您可能会遇到几个问题。当我在我的应用程序中实现自定义缓存时,我遇到了这样的噩梦,它最终与 NSURLCache 没有任何共同之处。此外,我决定创建一种基于 NSURLSession 和 AFNetworking 的框架。

iOS 7 上的一个问题是使用defaultConfiguration 创建NSURLSessionNSURLCache 对象将与全局对象不同 - 非常出乎意料的行为。 iOS 8 上的问题之一是 POST 请求也被缓存,这可能会危及您的应用程序的安全性。顺便说一句https://www.google.pl/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=nsurlcache+ios+8+broken

也许它不能回答您的问题,但您应该注意 iOS 版本之间的奇怪行为和差异。 老实说,我建议你使用 AFHTTPRequestOperationManager 而不是 AFHTTPSessionManager,因为至少 NSURLConnection 对 NSURLCache 是有害的 :)

【讨论】:

以上是关于AFHTTPSessionManager 上的 setDataTaskWillCacheResponseBlock 设置的块永远不会被调用的主要内容,如果未能解决你的问题,请参考以下文章

快速使用 AFHTTPSessionManager

使用 AFNetworking 2.0 的 POST 请求 - AFHTTPSessionManager

AFNetworking 2.0 AFHTTPSessionManager

使用 AFHTTPSessionManager 时的内存泄漏

如何在 AFHTTPSessionManager 请求中添加原始正文?

Swift - 为啥在 AFHTTPSessionManager 中需要 init(coder)?