有一些開發者可能會遇到一種情況,當伺服器端的圖片改變而url沒改變的時候,客戶端的圖片不會更新成最新的圖片。這是為什麼呢,往下看 -->
方法一:
1、
我們來簡單探討一下SDWebImage的實現原理: SDWebImageManager內部利用SDWebImageDownloader來下載,它的快取策略有兩種,一種是用NSURL快取,一種是自己定義了SDImageCache(內部使用NSCache)進行快取。 如果設定了SDWebImageRefreshCached標示位,那麼SDWebImageDownloader則利用NSURL進行快取,而且使用的policy為NSURLRequestUseProtocolCachePolicy。 那麼如果設定了SDWebImageRefreshCached標識位,圖片是否更新則要取決於你伺服器的cache-control設定了,如果沒有cache-control的話,客戶端則然享受不了自動更新的功能。 所以說僅僅設定SDWebImageRefreshCached往往是不能解決問題的。。。。 那麼如何檢視伺服器是否支援cache-control呢? 其實簡單,只需要要終端輸入
curl [url] --head
這就可以了。2、
基於這一現象,我們來進行分析。 客戶端第一次請求圖片時,Charles抓包得知response header裡有一個名為Last-Modified、資料是時間戳的鍵值對。
客戶端第二次及以後請求圖片時,通過Charles抓包發現,伺服器返回304 not modified狀態,說明伺服器在接收客戶端請求後通過某種判斷邏輯得出結論:“客戶端已快取的圖片與伺服器圖片都是最新的”,那麼伺服器如何判斷的呢?
通過查閱HTTP協議相關的資料得知,與伺服器返回的Last-Modified相對應的request header裡可以加一個名為If-Modified-Since的key,value即是伺服器回傳的服務端圖片最後被修改的時間,第一次圖片請求時If-Modified-Since的值為空,第二次及以後的客戶端請求會把伺服器回傳的Last-Modified值作為If-Modified-Since的值傳給伺服器,這樣伺服器每次接收到圖片請求時就將If-Modified-Since與Last-Modified進行比較,如果客戶端圖片已陳舊那麼返回狀態碼200、Last-Modified、圖片內容,客戶端儲存Last-Modified和圖片;如果客戶端圖片是最新的那麼返回304 Not Modified、不會返回Last-Modified、圖片內容。
關於伺服器的比較邏輯,需要強調一下。
經查資料得知,Apache比較時是看If-Modified-Since之後有沒有更新圖片,Nginx比較時是看If-Modified-Since與Last-Modified是否相等,所以對於Apache伺服器環境客戶端每次都要嚴格的儲存伺服器回傳的Last-Modified以便下次請求時作為If-Modified-Since的值傳給伺服器,對於Nginx伺服器環境客戶端不必儲存伺服器回傳的Last-Modified,每次請求時只需將圖片自身的fileModificationDate作為If-Modified-Since的值傳伺服器即可。在實際開發中,如果遇到明明傳了If-Modified-Since、伺服器圖片也變更了、但是客戶端卻請求不到最新的圖片的情況時,那麼就需要檢視一下伺服器對這兩個時間戳的比較邏輯。
那麼,現在我們可以回到SDWebImage上來了。通過檢視SDWebImageDownloader的原始碼得知,它開放了一個headersFilter的block,意在讓開發者可以對所有圖片請求追加一些額外的header,這正合我意。那麼我們就可以在諸如AppDelegate didFinishLaunching的地方追加如下程式碼:
SDWebImageDownloader *imgDownloader = SDWebImageManager.sharedManager.imageDownloader; imgDownloader.headersFilter = ^NSDictionary *(NSURL *url, NSDictionary *headers) {
NSFileManager *fm = [[NSFileManager alloc] init];
NSString *imgKey = [SDWebImageManager.sharedManager cacheKeyForURL:url];
NSString *imgPath = [SDWebImageManager.sharedManager.imageCache defaultCachePathForKey:imgKey];
NSDictionary *fileAttr = [fm attributesOfItemAtPath:imgPath error:nil];
NSMutableDictionary *mutableHeaders = [headers mutableCopy];
NSDate *lastModifiedDate = nil;
if (fileAttr.count > 0) {
if (fileAttr.count > 0) {
lastModifiedDate = (NSDate *)fileAttr[NSFileModificationDate];
}
}
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
formatter.dateFormat = @"EEE, dd MMM yyyy HH:mm:ss z";
NSString *lastModifiedStr = [formatter stringFromDate:lastModifiedDate];
lastModifiedStr = lastModifiedStr.length > 0 ? lastModifiedStr : @"";
[mutableHeaders setValue:lastModifiedStr forKey:@"If-Modified-Since"];
return mutableHeaders;
複製程式碼
};
然後,載入圖片的地方以前怎麼寫還是怎麼寫,但別忘了Option是SDWebImageRefreshCached
NSURL *imgURL = [NSURL URLWithString:@"handy-img-storage.b0.upaiyun.com/3.jpg"];
[[self imageView] sd_setImageWithURL:imgURL placeholderImage:nil options:SDWebImageRefreshCached];
方法二:
在SDWebImageManager.m大約167行的
if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
複製程式碼
}
中間加上:
// remove SDWebImageDownloaderUseNSURLCache flag downloaderOptions &= ~SDWebImageDownloaderUseNSURLCache;
變成:
if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// remove SDWebImageDownloaderUseNSURLCache flag
downloaderOptions &= ~SDWebImageDownloaderUseNSURLCache;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}複製程式碼