All http responses from a server come with the headers that inform our app not to cache the responses:
来自服务器的所有http响应都带有标题,告知我们的应用不要缓存响应:
Cache-Control: no-cache
Pragma: no-cache
Expires: 0
So, if you are making NSUrlRequests with default cache policy "NSURLRequestUseProtocolCachePolicy" then the app will always load data from the server. However, we need to cache the responses and the obvious solution would be to set these headers to some time for example (at backend side), set to 10 seconds. But I'm interested in a solution how to bypass this policy and cache every request for 10 seconds.
因此,如果您使用默认缓存策略“NSURLRequestUseProtocolCachePolicy”创建NSUrlRequests,则应用程序将始终从服务器加载数据。但是,我们需要缓存响应,显而易见的解决方案是将这些标头设置为某个时间(例如在后端),设置为10秒。但是我对如何绕过这个策略并将每个请求缓存10秒的解决方案感兴趣。
For that you need to setup shared cache. That might be done in AppDelegate didFinishLaunchingWithOptions:
为此,您需要设置共享缓存。这可能在AppDelegate didFinishLaunchingWithOptions中完成:
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:20 * 1024 * 1024
diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
Then, we need to embed our code to force to cache a response. If you use an instance of AFHttpClient then it can be done by overriding the method below and manually storing the cache into the shared cache:
然后,我们需要嵌入我们的代码来强制缓存响应。如果您使用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];
}
And the last thing is to set cachePolicy for the requests. In our case we want to set the same cache policy to all requests. So again, if you use an instance of AFHttpClient then it can be done by overriding the method below:
最后一件事是为请求设置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;
}
So far so good. "NSURLRequestReturnCacheDataElseLoad" makes to perform request first time and load response from cache all other times. The problem is, it's unclear how to set cache expiration time, for example 10 seconds.
到现在为止还挺好。 “NSURLRequestReturnCacheDataElseLoad”使得第一次执行请求并在其他时间从缓存加载响应。问题是,目前还不清楚如何设置缓存过期时间,例如10秒。
3 个解决方案
#1
10
You can implement a custom NSURLCache that only returns cached responses that has not expired.
您可以实现仅返回尚未过期的缓存响应的自定义NSURLCache。
Example:
#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
If this does not give you enough control then I would implement a custom NSURLProtocol with a startLoading method as below and use it in conjunction with the custom cache.
如果这没有给你足够的控制,那么我将使用如下的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];
}
}
Some links:
- Useful info on NSURLCache
- Creating a custom NSURLProtocol
有关NSURLCache的有用信息
创建自定义NSURLProtocol
#2
7
If somebody would be interested, here is Stephanus answer rewritten in Swift:
如果有人会感兴趣,这里是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)
}
}
#3
1
Another possible solution is to modify the response object and clobber the Cache-Control headers from the server and replace them with your own desired values.
另一种可能的解决方案是修改响应对象并从服务器中删除Cache-Control标头,并用您自己想要的值替换它们。
There are two places you could do that.
你可以做两个地方。
You could do it in a NSURLSessionDataDelegate
in func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse?) -> Void)
, but if you do it there, then can no longer use the usual completion handler-based methods getting results from session tasks.
您可以在func URLSession中的NSURLSessionDataDelegate中执行此操作(会话:NSURLSession,dataTask:NSURLSessionDataTask,willCacheResponse proposedResponse:NSCachedURLResponse,completionHandler:(NSCachedURLResponse?) - > Void),但如果您在那里执行,则无法再使用通常的完成基于处理程序的方法从会话任务中获取结果。
Another place you could do it is by defining a custom NSURLProtocol
, which intercepts HTTP and HTTPS responses and modifies them.
您可以做的另一个地方是定义一个自定义NSURLProtocol,它拦截HTTP和HTTPS响应并修改它们。
#1
10
You can implement a custom NSURLCache that only returns cached responses that has not expired.
您可以实现仅返回尚未过期的缓存响应的自定义NSURLCache。
Example:
#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
If this does not give you enough control then I would implement a custom NSURLProtocol with a startLoading method as below and use it in conjunction with the custom cache.
如果这没有给你足够的控制,那么我将使用如下的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];
}
}
Some links:
- Useful info on NSURLCache
- Creating a custom NSURLProtocol
有关NSURLCache的有用信息
创建自定义NSURLProtocol
#2
7
If somebody would be interested, here is Stephanus answer rewritten in Swift:
如果有人会感兴趣,这里是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)
}
}
#3
1
Another possible solution is to modify the response object and clobber the Cache-Control headers from the server and replace them with your own desired values.
另一种可能的解决方案是修改响应对象并从服务器中删除Cache-Control标头,并用您自己想要的值替换它们。
There are two places you could do that.
你可以做两个地方。
You could do it in a NSURLSessionDataDelegate
in func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse?) -> Void)
, but if you do it there, then can no longer use the usual completion handler-based methods getting results from session tasks.
您可以在func URLSession中的NSURLSessionDataDelegate中执行此操作(会话:NSURLSession,dataTask:NSURLSessionDataTask,willCacheResponse proposedResponse:NSCachedURLResponse,completionHandler:(NSCachedURLResponse?) - > Void),但如果您在那里执行,则无法再使用通常的完成基于处理程序的方法从会话任务中获取结果。
Another place you could do it is by defining a custom NSURLProtocol
, which intercepts HTTP and HTTPS responses and modifies them.
您可以做的另一个地方是定义一个自定义NSURLProtocol,它拦截HTTP和HTTPS响应并修改它们。