绕过http响应头Cache-Control:如何设置缓存过期?

来自服务器的所有http响应都带有标题,告知我们的应用不要缓存响应:

Cache-Control: no-cache Pragma: no-cache Expires: 0 

因此,如果您使用默认缓存策略“NSURLRequestUseProtocolCachePolicy”创建NSUrlRequests,则应用程序将始终从服务器加载数据。 但是,我们需要缓存响应,显而易见的解决方案是将这些标头设置为某个时间(例如在后端),设置为10秒。 但是我对如何绕过这个策略并将每个请求缓存10秒的解决方案感兴趣。

为此,您需要设置共享缓存。 这可能在AppDelegate didFinishLaunchingWithOptions中完成:

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

然后,我们需要嵌入我们的代码来强制缓存响应。 如果您使用AFHttpClient的实例,则可以通过覆盖以下方法并手动将缓存存储到共享缓存中来完成:

 - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { NSMutableDictionary *mutableUserInfo = [[cachedResponse userInfo] mutableCopy]; NSMutableData *mutableData = [[cachedResponse data] mutableCopy]; NSURLCacheStoragePolicy storagePolicy = NSURLCacheStorageAllowedInMemoryOnly; // ... return [[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response] data:mutableData userInfo:mutableUserInfo storagePolicy:storagePolicy]; } 

最后一件事是为请求设置cachePolicy。 在我们的例子中,我们希望为所有请求设置相同的缓存策略。 所以再次,如果您使用AFHttpClient的实例,那么可以通过覆盖以下方法来完成:

 - (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters { NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters]; request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; return request; } 

到现在为止还挺好。 “NSURLRequestReturnCacheDataElseLoad”使得第一次执行请求并在其他时间从缓存加载响应。 问题是,目前还不清楚如何设置缓存过期时间,例如10秒。

您可以实现仅返回尚未过期的缓存响应的自定义NSURLCache。

例:

 #import "CustomURLCache.h" NSString * const EXPIRES_KEY = @"cache date"; int const CACHE_EXPIRES = -10; @implementation CustomURLCache // static method for activating this custom cache +(void)activate { CustomURLCache *urlCache = [[CustomURLCache alloc] initWithMemoryCapacity:(2*1024*1024) diskCapacity:(2*1024*1024) diskPath:nil] ; [NSURLCache setSharedURLCache:urlCache]; } -(NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request { NSCachedURLResponse * cachedResponse = [super cachedResponseForRequest:request]; if (cachedResponse) { NSDate* cacheDate = [[cachedResponse userInfo] objectForKey:EXPIRES_KEY]; if ([cacheDate timeIntervalSinceNow] < CACHE_EXPIRES) { [self removeCachedResponseForRequest:request]; cachedResponse = nil; } } return cachedResponse; } - (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request { NSMutableDictionary *userInfo = cachedResponse.userInfo ? [cachedResponse.userInfo mutableCopy] : [NSMutableDictionary dictionary]; [userInfo setObject:[NSDate date] forKey:EXPIRES_KEY]; NSCachedURLResponse *newCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:cachedResponse.response data:cachedResponse.data userInfo:userInfo storagePolicy:cachedResponse.storagePolicy]; [super storeCachedResponse:newCachedResponse forRequest:request]; } @end 

如果这没有给你足够的控制,那么我将使用如下的startLoading方法实现自定义NSURLProtocol,并将其与自定义缓存结合使用。

 - (void)startLoading { NSMutableURLRequest *newRequest = [self.request mutableCopy]; [NSURLProtocol setProperty:@YES forKey:@"CacheSet" inRequest:newRequest]; NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]; if (cachedResponse) { [self connection:nil didReceiveResponse:[cachedResponse response]]; [self connection:nil didReceiveData:[cachedResponse data]]; [self connectionDidFinishLoading:nil]; } else { _connection = [NSURLConnection connectionWithRequest:newRequest delegate:self]; } } 

一些链接:

  • 有关NSURLCache的有用信息
  • 创建自定义NSURLProtocol

如果有人会感兴趣,这里是Stephanus在Swift中重写的答案:

 class CustomURLCache: NSURLCache { // UserInfo expires key let kUrlCacheExpiresKey = "CacheData"; // How long is cache data valid in seconds let kCacheExpireInterval:NSTimeInterval = 60*60*24*5; // get cache response for a request override func cachedResponseForRequest(request:NSURLRequest) -> NSCachedURLResponse? { // create empty response var response:NSCachedURLResponse? = nil // try to get cache response if let cachedResponse = super.cachedResponseForRequest(request) { // try to get userInfo if let userInfo = cachedResponse.userInfo { // get cache date if let cacheDate = userInfo[kUrlCacheExpiresKey] as NSDate? { // check if the cache data are expired if (cacheDate.timeIntervalSinceNow < -kCacheExpireInterval) { // remove old cache request self.removeCachedResponseForRequest(request); } else { // the cache request is still valid response = cachedResponse } } } } return response; } // store cached response override func storeCachedResponse(cachedResponse: NSCachedURLResponse, forRequest: NSURLRequest) { // create userInfo dictionary var userInfo = NSMutableDictionary() if let cachedUserInfo = cachedResponse.userInfo { userInfo = NSMutableDictionary(dictionary:cachedUserInfo) } // add current date to the UserInfo userInfo[kUrlCacheExpiresKey] = NSDate() // create new cached response let newCachedResponse = NSCachedURLResponse(response:cachedResponse.response, data:cachedResponse.data, userInfo:userInfo,storagePolicy:cachedResponse.storagePolicy) super.storeCachedResponse(newCachedResponse, forRequest:forRequest) } } 

另一种可能的解决方案是修改响应对象并从服务器中删除Cache-Control标头,并用您自己想要的值替换它们。

你可以做两个地方。

您可以在func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse?) -> Void)中的NSURLSessionDataDelegate中执行此func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse?) -> Void) ,但如果您在那里执行,则无法再使用通常的完成基于处理程序的方法从会话任务中获取结果。

您可以做的另一个地方是定义一个自定义NSURLProtocol ,它拦截HTTP和HTTPS响应并修改它们。