上篇文章 咱们已经针对
NSCache
的底层了解了其具体的实现机制和淘汰策略 . 因为文章太长 , 不利于阅读 . 那么这篇文章 , 咱们就NSURLCache
以及SDWebImage
中的缓存处理机制进行探究讲解.nginx
目录我就继续上篇文章的来了 , 以便比较阅读.算法
先把官方文档奉上 NSURLCache数据库
首先咱们都知道 , 使用 NSURLCache
进行请求数据的缓存时 , 同时自己默认也会有缓存的处理. 那么咱们须要作什么 ? 原生默认作了什么 ?浏览器
The
NSURLCache
class implements the caching of responses to URL load requests by mappingNSURLRequest
objects toNSCachedURLResponse
objects. It provides a composite in-memory and on-disk cache, and lets you manipulate the sizes of both the in-memory and on-disk portions. You can also control the path where cache data is stored persistently.缓存
啥意思呢 ? 重点就是 它提供了磁盘缓存和内存缓存 , 并可让用户来指定缓存的磁盘和内存的大小 . 至于其余的何时来清楚这些磁盘或者内存中的缓存内容咱们无需关心.bash
而且它提供给咱们集中不一样的策略以知足灵活多变的需求.服务器
当使用 NSURLSession
时 , 咱们能够直接经过 NSMutableURLRequest
来指定 NSURLRequestCachePolicy
.网络
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
NSURLRequestUseProtocolCachePolicy = 0, //默认策略
NSURLRequestReloadIgnoringLocalCacheData = 1, //忽略缓存,必须从远程地址下载;
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented 未实现
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
NSURLRequestReturnCacheDataElseLoad = 2, // 不管缓存是否过时 , 有就使用缓存 , 没有就请求数据.
NSURLRequestReturnCacheDataDontLoad = 3,// 不管缓存是否过时 , 有就使用缓存 , 没有就请求失败.
NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented 未实现
};
复制代码
在了解 NSURLCache
以前 , 先来看看默认的 HTTP 缓存机制. 也就是 NSURLRequestCachePolicy
的默认策略.session
先来个默认使用 HTTP 缓存策略的例子.app
- (void)example_1{
NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/50x50.jpg"];
//默认使用 HTTP缓存策略来进行缓存
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"error warning : %@",error);
}else{
//从缓存当中读取数据!
NSData *tempData = data;
NSString *responseStr = [[NSString alloc] initWithData:tempData encoding:NSUTF8StringEncoding];
NSLog(@"response:%@",response);
}
}] resume];
}
复制代码
打印结果:
response:<NSHTTPURLResponse: 0x600002ff7380> { URL: http://via.placeholder.com/50x50.jpg } { Status Code: 200, Headers {
"Accept-Ranges" = (
bytes
);
"Cache-Control" = (
"max-age=604800"
);
Connection = (
"keep-alive"
);
"Content-Length" = (
807
);
"Content-Type" = (
"image/jpeg"
);
Date = (
"Wed, 18 Sep 2019 06:32:38 GMT"
);
Etag = (
"\"5d5c8aae-327\""
);
Expires = (
"Wed, 25 Sep 2019 06:32:38 GMT"
);
"Last-Modified" = (
"Wed, 21 Aug 2019 00:05:02 GMT"
);
Server = (
"nginx/1.6.2"
);
"X-Cache" = (
L1
);
} }
复制代码
其实在 HTTP 中,控制缓存开关的字段有两个:Pragma
和 Cache-Control
. Pragma
是旧产物,已经逐步抛弃,有些网站为了向下兼容还保留了这两个字段. 在此就不介绍了.
在请求中使用 Cache-Control
时,它可选的值有:
在缓存中,咱们须要一个机制来验证缓存是否有效。好比服务器的资源更新了,客户端须要及时刷新缓存;又或者客户端的资源过了有效期,但服务器上的资源仍是旧的,此时并不须要从新发送。缓存校验就是用来解决这些问题的,
在 HTTP 1.1 中,咱们主要关注下
Last-Modified
和etag
这两个字段。
服务端在返回资源时,会将该资源的最后更改时间经过 Last-Modified
字段返回给客户端。客户端下次请求时经过 If-Modified-Since
或者 If-Unmodified-Since
带上 Last-Modified
,服务端检查该时间是否与服务器的最后修改时间一致:
If-Modified-Since
和 If-Unmodified-Since
的区别是:
If-Modified-Since
:告诉服务器若是时间一致,返回状态码304
If-Unmodified-Since
:告诉服务器若是时间不一致,返回状态码412
单纯的以修改时间来判断仍是有缺陷,好比文件的最后修改时间变了,但内容没变。对于这样的状况,咱们可使用 Etag
来处理。
Etag
机制:
服务器经过某个算法对资源进行计算,取得一串值(相似于文件的
hash
值),以后将该值经过Etag
返回给客户端,客户端下次请求时经过If-None-Match
或If-Match
带上该值,服务器对该值进行对比校验:若是一致则不要返回资源。
If-None-Match
和 If-Match
的区别是:
If-None-Match
:告诉服务器若是一致,返回状态码 304
,不一致则返回资源If-Match
:告诉服务器若是不一致,返回状态码 412
你可能会以为使用 Last-Modified
已经足以让客户端知道本地的缓存副本是否足够新,为何还须要 Etag
(实体标识)呢?HTTP 1.1 中 Etag
的出现主要是为了解决几个 Last-Modified
比较难解决的问题:
Last-Modified
标注的最后修改只能精确到秒级,若是某些文件在 1 秒钟之内,被修改屡次的话,它将不能准确标注文件的修改时间
若是某些文件会被按期生成,当有时内容并无任何变化,但 Last-Modified
却改变了,致使文件无法使用缓存
有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形
Etag
是服务器自动生成或者由开发者生成的对应资源在服务器端的惟一标识符,可以更加准确的控制缓存。Last-Modified
与ETag
是能够一块儿使用的,服务器会优先验证ETag
,一致的状况下,才会继续比对Last-Modified
,最后才决定是否返回 304。
缓存开关是: pragma
, cache-control
.
缓存校验有:Expires
, Last-Modified
, etag
.
从整个流程来看 , 他们以下图: ( 图片源自网络 : 浏览器加载 HTTP 缓存机制 )
说了这么多 , 咱们刚刚写的案例到底缓存了什么内容呢 . 加个断点 获取一下沙盒 , 而后咱们打开文件夹看下 :
DB Browser for SQLite
cfurl_cache_blob_data
浏览数据.
response_object
右边导出二进制 bin
文件 , 使用终端直接 cat
命令查看文件.
能够看到 : 本次 HTTP 请求信息都被存储到这个数据库表中.
再继续查看你会发现全部的信息都在各个表中存储完毕.
仍是以前的案例 , 咱们稍微修改一下:
- (void)example_1{
NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/50x50.jpg"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];
//默认使用 HTTP缓存策略来进行缓存
// NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];\
//比对服务,资源是否更新
if (self.lastModified) {
[request setValue:self.lastModified forHTTPHeaderField:@"If-Modified-Since"];
}
// if (self.etag) {
// [request setValue:self.etag forHTTPHeaderField:@"If-None-Match"];
// }
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"error warning : %@",error);
}else{
//从缓存当中读取数据!
NSData *tempData = data;
NSString *responseStr = [[NSString alloc] initWithData:tempData encoding:NSUTF8StringEncoding];
self.lastModified = [(NSHTTPURLResponse *)response allHeaderFields][@"Last-Modified"];
// self.etag = [(NSHTTPURLResponse *)response allHeaderFields][@"Etag"];
NSLog(@"response:%@",response);
}
}] resume];
}
- (IBAction)reloadDataAction:(id)sender {
[self example_1];
}
复制代码
运行 , 第一次加载 , 返回 200
, 点击 reload
再加载一次 . 打印以下:
lastModified
或者
etag
的用法.
直接打开 SDWebImage
源码 . 来到 SDWebImageDownloader.m
的 以下方法中.
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
__strong __typeof (wself) sself = wself;
NSTimeInterval timeoutInterval = sself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
/*如下省略...*/
}
复制代码
里面有这么一段值得注意的 :
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
也就是说 , 除非用户指定了 options
是 UseNSURLCache
, 不然 SD
会默认忽略掉 NSURLCache
.
这么作的目的 上面也写了注释 , 防止屡次缓存 , 避免 SD
本身实现的缓存和 NSURLCache
屡次缓存形成资源浪费 .
一样的 , 在 SDWebImageDownloaderOperation.m
里, NSURLSessionDataDelegate
的代理方法 willCacheResponse
中 也能够看出来:
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
NSCachedURLResponse *cachedResponse = proposedResponse;
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
cachedResponse = nil;
}
if (completionHandler) {
completionHandler(cachedResponse);
}
}
复制代码
当设置 NSURLRequestReloadIgnoringLocalCacheData
策略 , 会忽略 NSURLCache
的缓存 .
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,
SDWebImageDownloaderUseNSURLCache = 1 << 2,
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
SDWebImageDownloaderContinueInBackground = 1 << 4,
SDWebImageDownloaderHandleCookies = 1 << 5,
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
SDWebImageDownloaderHighPriority = 1 << 7,
SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};
复制代码
那咱们一样来搜一下 SDWebImageDownloaderIgnoreCachedResponse
这个 options
时 , SD
作了哪些处理.
SDWebImageDownloaderOperation.m
的 start
方法中有这么一段:if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// Grab the cached data for later check
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
if (cachedResponse) {
self.cachedData = cachedResponse.data;
}
}
复制代码
加载 NSURLCache
的磁盘缓存数据 , 以便下面作判断用.
SDWebImageDownloaderOperation.m
的 didCompleteWithError
代理方法中 有一段:if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
// call completion block with nil
[self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
}
复制代码
这里就直接返回了 nil
, 也就是说 SDWebImageDownloaderIgnoreCachedResponse
这个 options
的机制就是当 image
是从 NSURLCache
获取到的时候 , 它会返回 nil
.
以上就是关于 NSURLCache
以及 SDWebImage
和 HTTP
的缓存策略分析. 若有错误 , 欢迎指正 .