最近使用NSUrlCache做网络缓存,感觉比之前自己建立数据库来缓存这些数据要简单的多,自己建立的数据库机制十分简陋,而是用系统提供的NSUrlCache 则更全面而稳定。我们不用费心去管理内存过大导致崩溃,也不用去管设置了固定容量之后新的返回值不能被缓存。
值得注意的是只有get 方法才会产生缓存,而post方法不会产生缓存,但是这个几乎没有什么影响,因为post的方法多用于上传,和修改等接口,没有缓存的必要。
使用这个方法可以整体设置 内存容量和硬盘容量以及存储位置。
NSURLCache *urlCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:20 * 1024 * 1024 diskPath:nil];
[NSURLCache setSharedURLCache:urlCache];
这段代码可以放到AppDelegate中,在程序启动时就设置好,其实这段代码都是默认值,如果不需要改变缓存的大小及位置,则不需要写这段代码,默认位置存储在
(user home directory)/Library/Caches/(application bundle id)
这个路径下,会生成一个数据库文件,如图所示。
这篇文章 对Cache.db 进行了很好的分析。
四种缓存策略
NSURLRequestReloadIgnoringLocalCacheData: 忽略缓存,必须从远程地址下载;
NSURLRequestReturnCacheDataElseLoad:只要本地有缓存就使用本地的缓存(不管过期时间),只有本地没有缓存的时候才使用远程地址下载;
NSURLRequestReturnCacheDataDontLoad:只从本地缓存获取内容,如果本地没有的话,也不会去远程地址下载(也就是离线模式);
NSURLRequestUseProtocolCachePolicy:默认缓存策略;
NSURLSession缓存设置
第一种直接url 访问
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler
这里不能对该 url 的 request 进行设置,缓存也使用的是默认缓存策略NSURLRequestUseProtocolCachePolicy。
第二种 request 访问
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
这种方法可以通过生成request,来设置这个URL 特有的缓存策略,例如:
NSURLRequest * request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
AFNetworking 缓存设置
AFNetworking 使用 sessioManager 来进行网络访问,代码如下
AFHTTPSessionManager * manager = [AFHTTPSessionManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager.requestSerializer setCachePolicy:NSURLRequestReturnCacheDataElseLoad];
[manager GET:@"http://www.jianshu.com/p/88719de97921" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"请求成功");
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"请求失败");
}];
主要可以通过这段代码来设置缓存策略
[manager.requestSerializer setCachePolicy:(NSURLRequestCachePolicy)];
NSURLSession 和 AFNetworking 缓存的对比
区别
虽然 AFNetworking 本质上也是使用 NSURLSession 来进行网络访问,但是测试过发现其返回结果还是有区别的,NSURLSession 的方法默认都是采用 NSURLRequestUseProtocolCachePolicy:默认缓存策略,在无网络的情况下如果有缓存,error 会返回nil,data 则返回上一次缓存的data。
而AFNetworking 在NSURLRequestUseProtocolCachePolicy 这个默认缓存策略下,无网络且有缓存的情况下 并不会进入 success 回调,而是进入 failure 回调,原因还需要探索一下。
相同点
NSURLRequestReturnCacheDataElseLoad(只要本地有缓存就使用本地的缓存(不管过期时间),只有本地没有缓存的时候才使用远程地址下载;)在这种缓存策略下,并且有缓存的情况下,AFN 无论是否断网都会进入 success 回调,NSURLSession 回调中 error 也会为nil。
缓存过期机制
最有效率的缓存是只有在服务端数据发生变化时,或者超过了服务器设置的缓存超时时间,才会去再次服务端请求数据。
首先,要设置成
NSURLRequestUseProtocolCachePolicy:默认缓存策略
其次就需要服务端的设置,主要是在响应头中设置 Cache-Control
Cache-Control
这个头必须由服务器端指定以开启客户端的 HTTP 缓存功能。这个头的值可能包含 max-age(缓存多久),是公共 public,还是私有 private,或者不缓存 no-cache等信息。详情请参阅 Cache-Control section of RFC 2616。
除了 Cache-Control以外,服务器也可能发送一些附加的头用于根据需要有条件地请求:
Last-Modified 顾名思义,是资源最后修改的时间戳,往往与缓存时间进行对比来判断缓存是否过期
ETag用于放置文件的唯一标识,比如文件MD5值。
对于最常用的 NSURLRequestUseProtocolCachePolicy, 我们需要有两个注意的地方,即使当一个请求在本地存在缓存的情况下,如果这个请求需要重新验证,那系统还是会发起一个 HEAD 请求到服务器上去确定内容是否已经被修改过,如果修改的话还是会重新下载;如果缓存内容不需要验证,那系统只需要确定缓存时间是不是已经过期就可以了。
不使用缓存策略
如果觉得缓存策略不太满意,可以将缓存策略设置成这个:
NSURLRequestReloadIgnoringLocalCacheData 忽略本地缓存,只从服务端获取
从服务端获取成功之后,会产生缓存。然后使用NSURLResponse 来取出某个 Request 对应的本地缓存,自己判断什么时候从服务端获取,什么时候读取本地缓存,就当作一个单独的数据库来使用。
NSCachedURLResponse *response = [cache cachedResponseForRequest:request];
推荐文章
- adow很好的一篇博客 被忽视的 NSURLCache
- adow另一篇很好的博客 说说 NSURLCache 中的那些坑
- Mattt Thompson撰写、 Ricky Tan 翻译的 NSURLCacahe
- 网络缓存扫盲