上篇文章 咱們已經針對
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
的緩存策略分析. 若有錯誤 , 歡迎指正 .