Overview
NSURLCache 通过将 NSURLRequest 对象映射到 NSCachedURLResponse对象来实现对URL请求响应的缓存。
它提供了内存和磁盘缓存方式,同时允许设置内存和磁盘部分的大小,还可以控制缓存数据持久存储的路径。
NSURLCache 是线程安全的。
NSURLConnection、NSURLSession和UIWebView默认都会使用NSURLCache,所有经过他们请求的数据都将被NSURLCache处理。
缓存策略 NSURLRequestCachePolicy
NSURLRequestUseProtocolCachePolicy
默认缓存策略,对于特定URL使用网络协议中实现的缓存策略。
该策略下,当客户端发起一个请求时首先会检查本地是否包含缓存,如果有缓存则检查缓存是否过期(根据缓存的响应头中的 “Cache-Control:max-age” 或者 Expires判断),如果没有过期则直接使用缓存,如果过期则会发出请求(请求头中包含 “If-Modified-Since” 或者 “If-None-Match”),此时服务器端对比资源 “Last-Modified” 或者 “Etags”(二者都存在的情况下下如果有一个不同则认为缓存已过期),如果不同则返回新数据,否则返回304 Not Modified继续使用缓存数据。
该策略下,通常对于 NSURLSession 不做任何设置,只要服务器端响应头部加上 Cache-Control:max-age:xxx 就可以使用缓存了。
NSURLRequestReloadIgnoringLocalCacheData
忽略缓存,直接请求原始数据。
NSURLRequestReturnCacheDataElseLoad
无论缓存是否过期,有缓存则使用缓存数据,否则请求原始数据。
NSURLRequestReturnCacheDataDontLoad
无论缓存是否过期,有缓存则使用缓存数据,否则视为失败,不会请求原始数据。
使用 NSURLSessionDataDelegate 来更改缓存策略
场景一:服务端没有在响应头中配置 “Cache-Control”,且当前客户端使用的策略是 “NSURLRequestUseProtocolCachePolicy” 时,可以通过NSURLSessionDataDelegate 修改响应头信息,来启用缓存,代码如下:
1 | - (void)URLSession:(NSURLSession *)session |
场景二:针对某些特殊的请求,可以设置只进行内存缓存,具体代码如下:
1 | - (void)URLSession:(NSURLSession *)session |
NSURLCache能缓存POST请求吗?
网上很多文章在讲到NSURLCache时都说“NSURLCache 无法缓存POST请求的响应”,但我实际测试发现是可以的,具体可以查看demo。大致代码如下:
1 | // 创建配置 |
如果请求成功,会在沙盒的Library/Caches目录下发现一个以BundleID命名的文件夹,这个文件夹里存放的就是NSURLCache缓存数据的数据库,也就是磁盘缓存的地址:
打开Cache.db,里面有如下几张表格:
其中cfurl_cahce_response存储着response相关信息,如下图可以看到刚才发出的POST请求的URL:
而cfurl_cache_blob_data存储着response的响应体信息的二进制:
通过上面的缓存可以看出,NSURLCache其实是可以缓存POST请求的。当然也可以在请求一次后,断网再请求如果能够获取响应,也可以证明。
获取NSURLCache的POST请求缓存
既然已经知道NSURLCache可以缓存POST请求了,下一步就试试能否获取,代码如下:
1 |
|
结果发现居然获取不到缓存,仔细检查代码确定跟发起请求时的参数完全一致,难道是cachedResponseForRequest方法有问题吗?于是又用GET请求了一次,并使用cachedResponseForRequest获取缓存,这次成功获取到了,代码如下:
1 | NSURLCache *cache = [NSURLCache sharedURLCache]; |
这又是为什么呢?同样的方法,GET能取到而POST却不可以。于是换了个参数,再次请求,发现结果依旧是GET请求的缓存能获取,而POST不可以。
抱着疑惑打开了上文提到的cache.db,也就是NSURLCache的磁盘缓存,打开cfurl_cache_response :
通过request_key,我们知道第一条是POST请求,而后面两条是GET请求,但是我明明是分别请求了两次GET请求和两次POST请求,还有一条POST请求的缓存呢?
回想一下POST请求和GET请求的不同,主要体现在POST请求参数是放在请求体中,而GET请求参数是放在URL中的,而上面的表格并没有任何请求体相关信息,只有一个request_key(就是请求的URL),对此我提出一个猜测:
cachedResponseForRequest: 方法是根据request中的URL来获取缓存的(request.url.absoluteString),而不包含请求体部分。
为了验证这个猜测,我们把上面获取POST请求缓存的代码进行修改,如下:
1 |
|
成功获取到了POST请求的缓存,由此得出结论:
1.NSURLCache 可以缓存POST请求的响应
2.NSURLCache 是根据请求的URL进行缓存的,与请求体无关
这样的结论也意味着,进行多次POST请求且仅仅是参数不同,记录只是最后一条缓存,之前的缓存都会被覆盖。如果想要缓存每一次的POST请求,就要自行实现缓存策略,而不是使用NSURLCache。
如何关闭 NSURLCache 缓存
既然 NSURLCache 并没有想象中那么美好,那就试试让它不要自动去缓存,以下提供两种方法关闭 NSURLCache 缓存。
第一种是将 NSURLCache 缓存大小设置为0,代码如下:
1 | NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil]; |
第二种是在请求头中设置 “Cache-Control” 为 “no-cache”
1 | [request setValue:@"no-cache" forHTTPHeaderField:@"Cache-Control"]; |
这两种方法无论在 NSURLRequestCachePolicy 哪种策略下,都不会去缓存。