缓存策略
我们做缓存的目的 :
- 没有网络的时候,读取硬盘中的缓存数据 加载上次瞬间的数据
- 在网络不好,下载速度慢的时候,避免空白页,先读取缓存,下载后更新数据 。
更具需要,需要做的缓存就是磁盘缓存 。需要数据之前,判断网络 。读取磁盘中上一次打开app的缓存 。
选取缓存技术
针对的功能的单一 。使用 NSURLCache
启用系统缓存
NSURLCache *urlCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:20 * 1024 * 1024 diskPath:nil];
[NSURLCache setSharedURLCache:urlCache];
需要的接口 :
- 判断缓存文件是否存在 如果没有创建
- 文件中是否有缓存内容 如果没有加载当前瞬间的数据到缓存文件
- 读取缓存路径中文件的缓存
- 清空缓存
实现
- 使用get请求
- 设置内存缓存大小 、磁盘缓存大笑 、缓存路径 。
判断缓存的时效性
服务器的文件存贮,大多采用资源变动后就重新生成一个链接的做法。
但同时也不排除不同文件使用同一个链接。那么如果服务端的file更改了,本地已经有了缓存。如何更新缓存?
- Last-Modified
顾名思义,是资源最后修改的时间戳,往往与缓存时间进行对比来判断缓存是否过期
- 在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个Last-Modified的属性标记此文件在服务期端最后被修改的时间,格式类似这样:
Last-Modified: Fri, 12 May 2006 18:53:33 GMT
- 客户端第二次请求此URL时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间之后文件是否有被修改过:
If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT
- 如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
-
ETag
ETag是一个可以与Web资源关联的记号(token)。它是一个 hash 值,用作 Request 缓存请求头,每一个资源文件都对应一个唯一的 ETag 值,服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端,以下是服务器端返回的格式:
ETag: "50b1c1d4f775c61:df3"
客户端的查询更新格式是这样的:
If-None-Match: W/"50b1c1d4f775c61:df3"
那么判断规则就是这样的 :
if LastModifiedFromServer != LastModifiedOnClien
GetFromServer
else
GetFromCache
/*!
@brief 如果本地缓存资源为最新,则使用使用本地缓存。如果服务器已经更新或本地无缓存则从服务器请求资源。
@details
步骤:
1. 请求是可变的,缓存策略要每次都从服务器加载
2. 每次得到响应后,需要记录住 LastModified
3. 下次发送请求的同时,将LastModified一起发送给服务器(由服务器比较内容是否发生变化)
@return 图片资源
*/
- (void)getData:(GetDataCompletion)completion {
NSURL *url = [NSURL URLWithString:kLastModifiedImageURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];
// // 发送 etag
// if (self.etag.length > 0) {
// [request setValue:self.etag forHTTPHeaderField:@"If-None-Match"];
// }
// 发送 LastModified
if (self.localLastModified.length > 0) {
[request setValue:self.localLastModified forHTTPHeaderField:@"If-Modified-Since"];
}
[[[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"];
// 获取并且纪录 LastModified
self.localLastModified = httpResponse.allHeaderFields[@"Last-Modified"];
// NSLog(@"%@", self.etag);
NSLog(@"%@", self.localLastModified);
dispatch_async(dispatch_get_main_queue(), ^{
!completion ?: completion(data);
});
}] resume];
}
NSURLRequest默认的cache policy是NSURLRequestUseProtocolCachePolicy, 是最能保持一致性的协议。
NSURLRequestReloadIgnoringCacheData 忽略缓存直接从原始地址下载
NSURLRequestReturnCacheDataElseLoad 只有在cache中不存在data时才从原始地址下载
NSURLRequestReturnCacheDataDontLoad 允许app确定是否要返回cache数据,如果使用这种协议当本地不存在response的时候,创建NSURLConnection or NSURLDownload实例时将会马上返回nil;这类似于离线模式,没有建立网络连接.
总结
ETag 是的功能与 Last-Modified 类似:服务端不会每次都会返回文件资源。客户端每次向服务端发送上次服务器返回的 ETag 值,服务器会根据客户端与服务端的 ETag 值是否相等,来决定是否返回 data,同时总是返回对应的 HTTP 状态码。客户端通过 HTTP 状态码来决定是否使用缓存。比如:服务端与客户端的 ETag 值相等,则 HTTP 状态码为 304,不返回 data。服务端文件一旦修改,服务端与客户端的 ETag 值不等,并且状态值会变为200,同时返回 data。
在官方给出的文档中提出 ETag 是首选的方式,优于 Last-Modified 方式。因为 ETag 是基于 hash ,hash 的规则可以自己设置,而且是基于一致性,是“强校验”。 Last-Modified 是基于时间,是弱校验,弱在哪里?比如说:如果服务端的资源回滚客户端的 Last-Modified 反而会比服务端还要新。
--
虽然 ETag 优于 Last-Modified ,但并非所有服务端都会支持,而 Last-Modified 则一般都会有该字段。 大多数情况下需要与服务端进行协调支持 ETag ,如果协商无果就只能退而求其次。
判断服务器是否支持etag
[[self.response allHeaderFields] valueForKey:@"ETag"]
思路
为资源分派 hash 值,然后对比服务端与本地缓存是否一致来决定是否需要更新缓存。
这种思路,在开发中经常使用,比如:处于安全考虑,登陆操作一般不会传输账号密码,而是传输对应的 hash 值-- token ,这里的 token 就可以看做一个 file 资源,如果想让一个用户登陆超时时间是三天,只需要在服务端每隔三天更改下 token 值,客户端与服务端值不一致,然后服务端返回 token 过期的提示。
注意
如果借助了Last-Modified
和ETag
,那么缓存策略则必须使用NSURLRequestReloadIgnoringCacheData
策略,忽略缓存,每次都要向服务端进行校验。
NSURLCache不能满足的情况
系统帮我们做的缓存,好处是自动,无需我们进行复杂的设置。坏处也恰恰是这个:不够灵活,不能自定义。只能指定一个缓存的总文件夹,不能分别指定每一个文件缓存的位置,更不能为每个文件创建一个文件夹,也不能指定文件夹的名称。缓存的对象也是固定的:只能是 GET请求的返回值。
瞬间的大图和缩略图缓存问题
要求 :使用同一个接口 ,在请求头中设置thumbnail 参数来区分 。
SDWebImageManager *sdmanger = [SDWebImageManager sharedManager];
[sdmanger.imageDownloader setValue:@"true" forHTTPHeaderField:@"thumbnail"];
通过上边给请求设置参数
[imageView sd_setImageWithURL:@"" placeholderImage:@"" options:SDWebImageHandleCookies|SDWebImageRefreshCached];
在下载图片的时候 ,通过options
来控制下载方式 ,因为我们需要额外添加cookes 。所以需要options:SDWebImageDownloaderHandleCookies
另外需要注意的是 ,sdwebimage默认的是根据url缓存图片 ,所以因为两次请求的url是一样的 ,第二次发送请求高清图的时候并不会重新发送请求,而是从 缓存中提取了第一次的缩略图 。
所以,options 的选项中还要额外加一个SDWebImageRefreshCached
这样两次请求 。并不会有两份缓存 。在请求缩略图的时候 ,禁止重复请求 。保证不会让缩略图覆盖掉高清图 。
sdwebimage 的缓存库是用url 做一次运算以后作为key 。value 是图片的磁盘地址 。地址是用url 和 图片名称 用 md5 提取摘 。