NSURLCache缓存不应缓存的随机响应。

7
我正在实现一款应用,该应用会向我们控制的rest-api发送很多网络调用。最近,我们决定在服务器端引入缓存头信息以节省宝贵的网络和服务器时间。由于我们不知道数据有效期有多长,因此我们没有发送“Cache-control: max-age”或“Expires”头信息,而只发送“Last-Modified”头信息和“E-tag”,因此我们总是访问服务器,但大多数情况下响应速度非常快,返回304。
起初似乎一切正常,许多请求都被缓存。然而,由于缓存,我在应用程序中遇到了一些随机数据错误。
由于某种我无法理解的原因,“某些请求在本地被缓存并被用作“更新”的数据而没有访问服务器”,即使他们实际上并没有更新。这个问题一直存在,直到过了一段时间。然后一切又像使用“cache-control”头信息一样正常访问服务器,但却没有使用它!所以我的问题是:
NSURLCache与NSURLConnection如何决定某个请求不需要在线进行访问,而原始请求却没有带有“Cache-control:max-age”或“Expires”头信息?是否有人有类似的经验?如何在不删除整个缓存的情况下解决它?
更多背景信息:
  • I am using AFNetworking, but it relies on NSURLConnection so I do not think it changes anything
  • The cache used is the default [NSURLCache sharedURLCache] instance
  • It is a GET request, and when I check the headers from the cached response this is what I get:

    po [response allHeaderFields]

    "Access-Control-Allow-Headers" = "Content-Type";
    "Access-Control-Allow-Methods" = "GET, POST, DELETE, PUT";
    "Access-Control-Allow-Origin" = "*";
    Connection = "keep-alive";
    "Content-Encoding" = gzip;
    "Content-Length" = 522;
    "Content-Type" = "application/json";
    Date = "Mon, 02 Sep 2013 08:00:38 GMT";
    Etag = "\"044ad6e73ccd45b37adbe1b766e6cd50c\"";
    "Last-Modified" = "Sat, 31 Aug 2013 10:36:06 GMT";
    Server = "nginx/1.2.1";
    "Set-Cookie" = "JSESSIONID=893A59B6FEFA51566023C14E3B50EE1E; Path=/rest-api/; HttpOnly";
    
  • I can not predict or reproduce when the error is going to happen so solutions that rely on deleting the cache are not an option.

  • I am using iOS5+

有没有可能设备和服务器之间存在时钟同步问题?规格说明客户端在这种情况下的行为是未定义的(由客户端自行决定适当的操作)。另外,您是否尝试过对afnetworking代码进行仪器化以获得更多的洞察力? - Alex
时间问题是我最初的想法,但我无法找到可重复的情况(它非常随机)。无论如何,它发生在具有正确时间的设备上,服务器端也正确,所以似乎不是这个原因 :( 谢谢你的建议! - Angel G. Olloqui
3个回答

4
如何使用NSURLCache和NSURLConnection一起决定特定请求不需要在线时,RFC 2616的第13.2节说:由于源服务器并不总是提供明确的过期时间,HTTP缓存通常分配启发式过期时间,采用使用其他标头值(例如Last-Modified时间)来估计合理的过期时间的算法。HTTP / 1.1规范没有提供具体的算法,但对其结果施加了最坏情况的限制。由于启发式过期时间可能会损害语义透明度,因此应谨慎使用它们,并鼓励源服务器尽可能提供明确的过期时间。因此,即使您没有为数据提供特定的生命周期,URL加载系统也可能决定缓存的数据足够“新鲜”。为了获得最佳结果,您应尝试在响应标头中提供特定的生命周期。如果无法添加此类标头,则可以更改请求。if-modified-since或cache-control都可以帮助您避免缓存数据。

1
这似乎是目前为止最好的答案。它没有解决问题,但至少 RFC 2616 段落解释了我正在经历的情况。谢谢! - Angel G. Olloqui

2

请确保您的NSURLRequest缓存策略设置为NSURLRequestReturnCacheDataElseLoad

如果您正在使用AFNetworking中的AFHTTPClient.m,则可以覆盖该方法

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

将第470行替换为以下内容

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:15];

您实际上正在告诉请求在服务器没有更新时加载缓存..如果服务器已更新,则它将忽略缓存并从服务器下载内容。
FYI:NSURLCache将数据存储在内存中..如果您想将数据存储在磁盘中,您可以使用我的类。这是链接:https://github.com/shoeb01717/Disc-Cache-iOS

谢谢,但是正如我解释的那样,问题不在于信息没有被更改,而恰恰相反:它在不应该被缓存的时候却进行了缓存。此外,iOS5会将缓存保存在磁盘上,因此自己实现一个类可能帮助不大,不过我会试一下。 - Angel G. Olloqui
1
@shoeb:顺便说一下,自从iOS5以来,NSURLCACHE让你可以选择磁盘或内存,并且还可以分配大小。 - Alex
谢谢,我知道iOS5的功能,但我制作的类还可以让你从应用程序的主捆绑包中返回缓存。 - Shoeb Amin

2
根据“在某些时候,请求会被本地缓存并用作“更新”的数据而不会访问服务器”的陈述,我相信您的请求是内存缓存的。
NSURLCache将数据缓存在内存中,而不是磁盘上。因此,让我解释一下可能发生的情况。
您启动应用程序。 进行网络服务调用。 它从服务器获取数据。 您再次发起调用,并从内存中获取响应,而无需向服务器发出调用,并显示结果。
您离开应用程序一段时间或重新启动应用程序。它检查内存中是否有数据。如果没有,则再次向服务器发出调用并重复相同的行为。
我建议您编写自己的磁盘缓存,而不是依赖于NSURLConnection和NSUrlCache。因为Apple尚未实施某些缓存策略。

谢谢Vailbhav。正如我所说,这实际上是缓存问题,但问题不在于某些内容未被缓存,而恰恰相反:不应该被缓存的内容已经被缓存了。此外,它保存在磁盘上,但这是预期的,因为在iOS5中神经网络缓存会保存在磁盘上。 - Angel G. Olloqui

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接