【问题标题】:Bypassing http response header Cache-Control: how to set cache expiration?绕过http响应头Cache-Control:如何设置缓存过期?
【发布时间】:2015-09-23 05:28:48
【问题描述】:

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

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

因此,如果您使用默认缓存策略“NSURLRequestUseProtocolCachePolicy”发出 NSUrlRequest,那么应用程序将始终从服务器加载数据。但是,我们需要缓存响应,显而易见的解决方案是将这些标头设置为某个时间(例如在后端),设置为 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 秒。

【问题讨论】:

    标签: ios objective-c afnetworking


    【解决方案1】:

    您可以实现一个自定义的 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];
        }
    }
    

    一些链接:

    【讨论】:

    • 精湛的答案和参考链接。
    • 大家好,我在我的项目中实施此解决方案时遇到了困难。我也在处理一个我无法控制的服务器,每个响应都有一个标题“Cache-Control”=“max-age=0, private, no-store, no-cache, must-revalidate”;我试过实现这个解决方案,只添加了一些日志调用。我可以看到 cachedResponseForRequest: 正在被调用,但 storeCachedResponse:forRequest: 从未被调用。我可能做错了什么?在请求中,我正在设置 NSURLRequestReturnCacheDataElseLoad
    【解决方案2】:

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

    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】:

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

      有两个地方可以做到这一点。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-11-17
        • 2019-01-20
        • 2022-01-24
        • 2016-08-14
        • 2017-08-31
        • 1970-01-01
        • 2021-11-10
        相关资源
        最近更新 更多