我观察了我的应用程序的一些奇怪的行为,有时缓存响应,有时不缓存它们(所有响应都有Cache-Control:max-age = 600)。
测试很简单:我做了一个test.php脚本,它只是设置标题并返回一个简单的JSON:
<?php
header('Content-Type: application/json');
header('Cache-Control: max-age=600');
?>
{
"result": {
"employeeId": "<?php echo $_GET['eId']; ?>",
"dateTime": "<?php echo date('Y-m-d H:i:s'); ?>'" }
}
这是我从PHP页面得到的回复:
HTTP/1.1 200 OK
Date: Thu, 28 Nov 2013 11:41:55 GMT
Server: Apache
X-Powered-By: PHP/5.3.17
Cache-Control: max-age=600
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json
{
"result": {
"employeeId": "",
"dateTime": "2013-11-28 11:41:55'"
}
}
然后我创建了一个简单的应用程序并添加了AFNetworking库。
当我用几个参数调用脚本时,缓存正常工作:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *params = @{
@"oId": @"4011",
@"eId": self.firstTest ? @"1" : @"0",
@"status": @"2031",
};
[manager GET:@"http://www.mydomain.co.uk/test.php" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"JSON: %@", responseObject);
NSLog(@"Cache current memory usage (after call): %d", [cache currentMemoryUsage]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
但是当我增加参数数量时,例如:
NSDictionary *params = @{
@"organizationId": @"4011",
@"organizationId2": @"4012",
@"organizationId3": @"4013",
@"organizationId4": @"4014",
@"organizationId5": @"4015",
@"organizationId6": @"4016",
@"eId": self.firstTest ? @"1" : @"0",
@"status": @"2031",
};
它不再起作用,并且每次调用它时都会执行一个新请求。
我做了很多测试,在我看来它与URL的长度有关,因为如果我包含这组参数:
NSDictionary *params = @{
@"oId": @"4011",
@"oId2": @"4012",
@"oId3": @"4013",
@"oId4": @"4014",
@"oId5": @"4015",
@"oId6": @"4016",
@"eId": self.firstTest ? @"1" : @"0",
@"status": @"2031",
};
它有效!!
我做了很多测试,这是我发现的唯一模式......
为了从等式中排除AFNetworking,我创建了另一个仅使用NSURLConnection的测试程序,我可以看到相同的行为,因此它不是AFNetworking,绝对是NSURLCache。这是另一个测试:
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.mydomain.co.uk/test.php?eId=%@&organizationId=4011&organizationId2=4012&organizationId3=4013&organizationId4=4014&organizationId5=4015&organizationId6=4016", self.firstTest ? @"1" : @"0"]]; // doesn't work
//NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.mydomain.co.uk/test.php?eId=%@&oId=4011&oId2=4012&oId3=4013&oId4=4014&oId5=4015&oId6=4016", self.firstTest ? @"1" : @"0"]]; // work
//NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.mydomain.co.uk/test.php?eId=%@", self.firstTest ? @"1" : @"0"]]; // work
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error == nil) {
// Parse data here
NSString *responseDataStr = [NSString stringWithUTF8String:[data bytes]];
NSLog(@"Response data: %@", responseDataStr);
}
我还尝试确定URL中有多少个字符会触发问题,但即使在这种情况下,我也会得到奇怪的结果:
这个长度为112个字符,不起作用:
这个长度为111个字符并且有效:
我已经重命名了PHP脚本,看看URL的第一部分是否重要,我又有一个奇怪的行为:
这个长度为106个字符,不起作用:
这个长度为105个字符并且有效:
所以我从页面名称中删除了3个字符,并且我的工作阈值低了6个字符。
有什么建议吗?
谢谢, DEM
答案 0 :(得分:4)
我正在目睹类似某些类似于NSURLCache未缓存的某些响应,我想出了另一个可能的原因:
在我的情况下,我能够确定未缓存的响应是使用 Chunked transfer-encoding 返回的响应。我在其他地方读过NSURLCache应该在iOS 6之后缓存那些但是出于某些原因它不适用于我的情况(iOS 7.1和8.1)。
我看到此处显示的示例响应也有Transfer-Encoding: chunked
标题。
可能是你的一些响应是使用分块编码(那些没有缓存的)返回的,而有些则不是(那些缓存的)?
我的后端也在Apache上运行PHP,我仍然无法弄清楚为什么会这样做...... 可能是一些Apache扩展...
无论如何,我认为这听起来比请求URL长度方案更合理。
修改强>
已经有一段时间了,但我终于可以确认,在我们的案例中,分块传输编码会导致响应不被缓存。我已经使用iOS 7.1,8.1,8.3和8.4进行了测试。
由于我了解在服务器上更改设置并不总是那么容易,因此对于使用AFNetworking 2和继承 AFHTTPSessionManager 的人,我有一个建议的解决方案。
您可以将您的子类添加为AFNetworking的 AFNetworkingTaskDidCompleteNotification 的观察者,其中包含您自己缓存响应所需的所有内容。这意味着:会话数据任务,响应对象和响应序列化程序处理之前的响应数据。
如果您的服务器仅对其少数响应使用分块编码,则可以在 - (void)didCompleteTask:中添加代码以仅选择性地缓存响应。因此,例如,您可以检查传输编码响应标头,或根据其他条件缓存响应。
下面的示例HTTPSessionManager子类缓存所有返回任何数据的响应:
<强> MyHTTPSessionManager.h 强>
@interface MyHTTPSessionManager : AFHTTPSessionManager
@end
<强> MyHTTPSessionManager.m 强>
#import "MyHTTPSessionManager.h"
@implementation MyHTTPSessionManager
+ (instancetype)sharedClient {
static MyHTTPClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSNotificationCenter defaultCenter] addObserver:_sharedClient selector:@selector(didCompleteTask:) name:AFNetworkingTaskDidCompleteNotification object:nil];
});
return _sharedClient;
}
- (void)didCompleteTask:(NSNotification *)notification {
NSURLSessionDataTask *task = notification.object;
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
NSData *responseData = notification.userInfo[AFNetworkingTaskDidCompleteResponseDataKey];
if (!responseData.length) {
// Do not cache empty responses.
// You could place additional checks above to cache responses selectively.
return;
}
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:responseData];
[[NSURLCache sharedURLCache] storeCachedResponse:cachedResponse forRequest:task.currentRequest];
}
我试图想出一些更干净的解决方案,但似乎AFNetworking没有提供回调或委托方法,它足够早地返回我们需要的所有东西 - 也就是说,在它被响应序列化器序列化之前。
希望人们会发现这有用:)
答案 1 :(得分:0)
您是否尝试配置?
NSURLRequestCachePolicy
NSURLRequest
+ (id)requestWithURL:(NSURL *)theURL cachePolicy:(NSURLRequestCachePolicy)cachePolicy timeoutInterval:(NSTimeInterval)timeoutInterval
这些常量用于指定与缓存响应的交互。
enum
{
NSURLRequestUseProtocolCachePolicy = 0,
NSURLRequestReloadIgnoringLocalCacheData = 1,
NSURLRequestReloadIgnoringLocalAndRemoteCacheData =4,
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
NSURLRequestReturnCacheDataElseLoad = 2,
NSURLRequestReturnCacheDataDontLoad = 3,
NSURLRequestReloadRevalidatingCacheData = 5
};
typedef NSUInteger NSURLRequestCachePolicy;
答案 2 :(得分:0)
您可以通过继承NSURLProtocol并覆盖startLoading来调查您的缓存响应来自sharedURLCache:
在AppDelegate应用程序中添加:didFinishLaunchingWithOptions:
[NSURLProtocol registerClass:[CustomURLProtocol class]];
然后创建NSURLProtocol(CustomURLProtol)的子类并覆盖startLoading
- (void)startLoading
{
self.cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
if (self.cachedResponse) {
[self.client URLProtocol:self
didReceiveResponse:[self.cachedResponse response]
cacheStoragePolicy:[self.cachedResponse storagePolicy]];
[self.client URLProtocol:self didLoadData:[self.cachedResponse data]];
}
[self.client URLProtocolDidFinishLoading:self];
}
self.cachedResponse是我添加的属性NSCachedURLResponse。您可以在此查看任何cachedResponse是否有任何问题。