一、缓存的意义
为了提高程序的运行速度,我们使用缓存,对于相同的数据请求,如果不使用缓存那么会造成两种影响:1、浪费用户流量 2、程序响应速度慢。
二、缓存的流程
1、第一次请求服务器时
a、使用服务器的数据展示到UI上
b、将服务器的数据缓存到沙盒中
此时,内存缓存有数据,硬盘缓存也有数据。
2、再次请求数据
a、应用程序没关闭
此时内存缓存有数据,硬盘缓存也有数据,再次请求数据会直接从内存缓存中取出数据。
b、应用程序重新打开
程序重新打开,内存缓存已经被清空,从硬盘缓存中取出数据展示,此时内存缓存也有数据,若再次加载数据,直接调用a步骤。
三、缓存的实现
1、缓存类为NSURLCache,可以做到完全的离线缓存,即在没有网络的情况下打开离线内容,通过自定义实现,将缓存文件放到沙河路径下,缓存空间没有大小限制。
2、NSURLCache拦截不到WKWebView中发出的任何网络请求。所以如果使用WKWebView的话,NSURLCache实现不了离线缓存的功能。
各个缓存策略介绍:
(1)NSURLRequestUseProtocolCachePolicy:NSURLRequest默认的cache policy,使用Protocol协议定义。
(2)NSURLRequestReloadIgnoringCacheData:忽略缓存直接从原始地址下载。
(3)NSURLRequestReturnCacheDataElseLoad:只有在cache中不存在data时才从原始地址下载。
(4)NSURLRequestReturnCacheDataDontLoad:只使用cache数据,如果不存在cache,请求失败;用于没有建立网络连接离线模式;
(5)NSURLRequestReloadIgnoringLocalAndRemoteCacheData:忽略本地和远程的缓存数据,直接从原始地址下载,与NSURLRequestReloadIgnoringCacheData类似。
(6)NSURLRequestReloadRevalidatingCacheData:验证本地数据与远程数据是否相同,如果不同则下载远程数据,否则使用本地数据。
(7)说明:5和6苹果暂未实现。
3、设置缓存大小
a、默认情况下,内存是4M ,硬盘为20M,缓存路径为系统设置。
[[NSURLCache sharedURLCache] setMemoryCapacity:4*1024*1024]。
[[NSURLCache sharedURLCache] setDiskCapacity:20*1024*1024]。
b、可以自己初始化缓存对象,然后设置缓存路径。
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4*1024*1024 diskCapacity:20*1024*1024 diskPath:path];
[NSURLCache setSharedURLCache:URLCache];
4、要点解析
a、NSURLCache包括磁盘缓存和内存缓存,磁盘缓存有默认的缓存路径,也可以自己指定缓存路径。
a1):设置自定义路径时需要注意:
设置 NSURLCache 时 diskPath 部分传参数时,注意只需要写文件夹名字即可,不需要写全路径。比如传入 @"123" 就会自动创建 Library/Caches/{bundleid}/123/。
重复设置 NSURLCache 时需要注意:如果先设置的默认路径,之后再设置自定义路径的 NSURLCache 时,如果设置前已经发生网络请求,并且已经在默认路径中写入了数据,那么就会出现错乱:虽然在新自定义路径中保存了cache,但读 cache 还是会从默认路径读取,造成每次都找不到cache而重新请求。
设置自定义路径后,之前默认的 cache 并不会清除。需要手动删除 [[NSURLCache sharedURLCache] removeAllCachedResponses];,记得在设置新缓存之前删除,否则 [NSURLCache sharedURLCache] 返回的就是旧有的缓存。可以升级app时删除,也可以在设置自定义路径时删除,做法参考 MRC 的 setter 操作。
b、系统存储空间不足时,当前的请求不会被缓存,包括之前磁盘的缓存也可能被系统清除掉。
c、如果使用NSURLCache,在应用收到内存警告时,要清空缓存:removeAllCachedResponses。
5、NSURLCache相关API
1.功能方法:
(1)- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request;
返回对应的NSURLRequest缓存的response,如果没有则返回nil。
(2)- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request;
为特定的NSURLRequest指定缓存对象,并存储。
(3)- (void)removeCachedResponseForRequest:(NSURLRequest *)request;
移除特定NSURLRequest的cache。
(4)- (void)removeAllCachedResponses;
移除所有的cache。
2.property方法
- (NSUInteger)memoryCapacity;
- (NSUInteger)diskCapacity;
- (void)setMemoryCapacity:(NSUInteger)memoryCapacity;
- (void)setDiskCapacity:(NSUInteger)diskCapacity;
- (NSUInteger)currentMemoryUsage;
- (NSUInteger)currentDiskUsage;
四、自定义NSURLCache
在一些特殊场景,如果要实现自定义的缓存机制,需要子类化NSURLCache,有链各个地方需要重写。
1、重写cachedResponseForRequest:(NSURLRequest *)request,这个会在请求发送前会被调用,从中我们可以判定是否针对此NSURLRequest返回本地数据。如果本地没有缓存就调用下面这条语句:return [super cachedResponseForRequest:request];。
2、重写storeCachedResponse:(NSCachedURLResponse )cachedResponse forRequest:(NSURLRequest )request,我们可以对某一个请求做我们自己的数据保存机制,如果使用系统默认的数据保存机制,则调用[super storeCachedResponse:cachedResponse forRequest:request];
重写示例如下:
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request{
//获取URL整体路径
NSString *urlStringMD5 = [self md5:request.URL.absoluteString];
//获取缓存文件存储地址
NSString *filePath = [[self getDocumentPath] stringByAppendingPathComponent:urlStringMD5];
//如果缓存存在,则返回缓存数据,否则使用系统默认处理
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
//获取缓存文件路径
NSData *fileData = [[NSData alloc] initWithContentsOfFile:filePath];
//根据URL路径,获取媒体类型
NSString *memiType = [self mimeTypeForPath:request.URL.absoluteString];
//合成NSCachedURLResponse对象,返回
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[request URL]
MIMEType:memiType
expectedContentLength:[fileData length]
textEncodingName:nil];
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:fileData];
return cachedResponse;
}else{
return [super cachedResponseForRequest:request];
}
}
- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request{
//将服务器返回数据缓存起来
NSString *urlStringMD5 = [self md5:request.URL.absoluteString];
NSString *filePath = [[self getDocumentPath] stringByAppendingPathComponent:urlStringMD5];
[cachedResponse.data writeToFile:filePath atomically:YES];
}
五、使用分析
/*!
@brief 如果本地缓存资源为最新,则使用使用本地缓存。如果服务器已经更新或本地无缓存则从服务器请求资源。
@details
步骤:
1. 请求是可变的,缓存策略要每次都从服务器加载
2. 每次得到响应后,需要记录住 etag
3. 下次发送请求的同时,将etag一起发送给服务器(由服务器比较内容是否发生变化)
@return 图片资源
*/
- (void)getData:(GetDataCompletion)completion {
NSURL *url = [NSURL URLWithString:kETagImageURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];
// 发送 etag
if (self.etag.length > 0) {
[request setValue:self.etag forHTTPHeaderField:@"If-None-Match"];
}
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// NSLog(@"%@ %tu", response, data.length);
// 类型转换(如果将父类设置给子类,需要强制转换)
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSLog(@"statusCode == %@", @(httpResponse.statusCode));
// 判断响应的状态码是否是 304 Not Modified (更多状态码含义解释: https://github.com/ChenYilong/iOSDevelopmentTips)
if (httpResponse.statusCode == 304) {
NSLog(@"加载本地缓存图片");
// 如果是,使用本地缓存
// 根据请求获取到`被缓存的响应`!
NSCachedURLResponse *cacheResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
// 拿到缓存的数据
data = cacheResponse.data;
}
// 获取并且纪录 etag,区分大小写
self.etag = httpResponse.allHeaderFields[@"Etag"];
NSLog(@"%@", self.etag);
dispatch_async(dispatch_get_main_queue(), ^{
!completion ?: completion(data);
});
}] resume];
}