SDWebImage和AFNetworking的緩存機制 - 奔跑的人
- 博客頻道 - CSDN.NET
一、SDWebImage緩存圖片的機制
SDWebImage是一個很厲害的圖片緩存的框架。既ASIHttp+AsyncImage之后宋税,我一直使用AFNetworking集成的UIImageView+AFNetworking.h靡努,但后者對于圖片的緩存實際應用的是NSURLCache自帶的cache機制坪圾。而NSURLCache每次都要把緩存的raw ?data 再轉化為UIImage,就帶來了數據處理和內存方面的更多操作惑朦。具體的比較在這里兽泄。
SDWebImage提供了如下三個category來進行緩存。
以最為常用的UIImageView為例:
UIImageView+WebCache:setImageWithURL:placeholderImage:options:先顯示 placeholderImage 漾月,同時由SDWebImageManager 根據 URL 來在本地查找圖片病梢。
SDWebImageManager:downloadWithURL:delegate:options:userInfo:SDWebImageManager是將UIImageView+WebCache同SDImageCache鏈接起來的類,?SDImageCache:queryDiskCacheForKey:delegate:userInfo:用來從緩存根據CacheKey查找圖片是否已經在緩存中
如果內存中已經有圖片緩存梁肿,?SDWebImageManager會回調SDImageCacheDelegate :imageCache:didFindImage:forKey:userInfo:
而 UIImageView+WebCache 則回調SDWebImageManagerDelegate:webImageManager:didFinishWithImage:來顯示圖片蜓陌。
如果內存中沒有圖片緩存,那么生成 NSInvocationOperation 添加到隊列吩蔑,從硬盤查找圖片是否已被下載緩存钮热。
根據 URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進行的操作烛芬,所以回主線程進行結果回調notifyDelegate:隧期。
如果上一操作從硬盤讀取到了圖片,將圖片添加到內存緩存中(如果空閑內存過小赘娄,會先清空內存緩存)仆潮。SDImageCacheDelegate 回調imageCache:didFindImage:forKey:userInfo:。進而回調展示圖片擅憔。
如果從硬盤緩存目錄讀取不到圖片鸵闪,說明所有緩存都不存在該圖片,需要下載圖片暑诸,回調imageCache:didNotFindImageForKey:userInfo:蚌讼。
共享或重新生成一個下載器SDWebImageDownloader開始下載圖片辟灰。
圖片下載由 NSURLConnection 來做,實現相關 delegate 來判斷圖片下載中篡石、下載完成和下載失敗芥喇。
connection:didReceiveData:中利用 ImageIO 做了按圖片下載進度加載效果。
connectionDidFinishLoading:數據下載完成后交給SDWebImageDecoder做圖片解碼處理凰萨。
圖片解碼處理在一個 NSOperationQueue 完成继控,不會拖慢主線程 UI。如果有需要對下載的圖片進行二次處理胖眷,最好也在這里完成武通,效率會好很多。
在主線程notifyDelegateOnMainThreadWithInfo:宣告解碼完成珊搀,imageDecoder:didFinishDecodingImage:userInfo:回調給 SDWebImageDownloader冶忱。
imageDownloader:didFinishWithImage:回調給 SDWebImageManager 告知圖片下載完成。
通知所有的 downloadDelegates 下載完成境析,回調給需要的地方展示圖片囚枪。
將圖片保存到 SDImageCache 中,內存緩存和硬盤緩存同時保存劳淆。
寫文件到硬盤在單獨 NSInvocationOperation 中完成链沼,避免拖慢主線程。
如果是在iOS上運行沛鸵,SDImageCache 在初始化的時候會注冊notification 到?UIApplicationDidReceiveMemoryWarningNotification 以及 ?UIApplicationWillTerminateNotification,在內存警告的時候清理內存圖片緩存括勺,應用結束的時候清理過期圖片。
SDWebImagePrefetcher可以預先下載圖片曲掰,方便后續(xù)使用朝刊。
二、(譯)緩存在AFNetworking中是如何工作的蜈缤?AFImageCache和NSUrlCache給你答案
如果你是一名使用Mattt Thompson網絡框架AFNetworking的iOS開發(fā)者(如果你不是拾氓,那還等什么呢?)底哥,也許你對這個框架中的緩存機制很好奇或者疑惑咙鞍,并想學習如何在自己的app中充分利用這種機制。
AFNetworking實際上使用了兩個獨立的緩存機制:
● AFImagecache:一個提供圖片內存緩存的類趾徽,繼承自NSCache续滋。
● NSURLCache:NSURLConnection's默認的URL緩存機制,用于存儲NSURLResponse對象:一個默認緩存在內存孵奶,通過配置可以緩存到磁盤的類疲酌。
為了理解每個緩存系統是如何工作的,我們看一下他們是如何定義的。
AFImageCache是如何工作的
AFImageCache是UIImageView+AFNetworking分類的一部分朗恳。它繼承自NSCache湿颅,通過一個URL字符串作為它的key(從NSURLRequest中獲取)來存儲UIImage對象粥诫。
AFImageCache定義:
46@interface?AFImageCache?:?NSCache?
//?singleton?instantiation?:
+?(id?)sharedImageCache?{
staticAFImageCache?*_af_defaultImageCache?=?nil;
staticdispatch_once_t?oncePredicate;
dispatch_once(&oncePredicate,?^{
_af_defaultImageCache?=?[[AFImageCache?alloc]?init];
//?clears?out?cache?on?memory?warning?:
[[NSNotificationCenter?defaultCenter]?addObserverForName:UIApplicationDidReceiveMemoryWarningNotification?object:nil?queue:[NSOperationQueue?mainQueue]?usingBlock:^(NSNotification?*?__unused?notification)?{
[_af_defaultImageCache?removeAllObjects];
}];
});
//?key?from?[[NSURLRequest?URL]?absoluteString]?:
staticinlineNSString?*?AFImageCacheKeyFromURLRequest(NSURLRequest?*request)?{
return[[request?URL]?absoluteString];
}
@implementation?AFImageCache
//?write?to?cache?if?proper?policy?on?NSURLRequest?:
-?(UIImage?*)cachedImageForRequest:(NSURLRequest?*)request?{
switch([request?cachePolicy])?{
caseNSURLRequestReloadIgnoringCacheData:
caseNSURLRequestReloadIgnoringLocalAndRemoteCacheData:
returnnil;
default:
break;
}
return[self?objectForKey:AFImageCacheKeyFromURLRequest(request)];
}
//?read?from?cache?:
-?(void)cacheImage:(UIImage?*)image?forRequest:(NSURLRequest?*)request?{
if(image?&&?request)?{
[self?setObject:image?forKey:AFImageCacheKeyFromURLRequest(request)];
}
}
AFImageCache 從 AFNetworking 2.1開始可以進行配置了油航。有一個公共方法setSharedImageCache。詳細文檔可以看這里 怀浆。它把所有可訪問的UIImage對象存到了NSCache谊囚。當UIImage對象釋放之后NSCache會進行處理。如果你想觀察images什么時候釋放执赡,可以實現NSCacheDelegate的cache:willEvictObject方法
NSURLCache如何工作
默認是可以的镰踏,但最好還是手動配置一下
既然AFNetworking使用NSURLConnection,它利用了原生的緩存機制NSURLCache沙合。NSURLCache緩存了從服務器返回的NSURLResponse對象余境。
NSURLCache的shareCache方法默認是可以使用的,緩存獲取的內容灌诅。不幸的是,它的默認配置只是緩存在內存并沒有寫到硬盤含末。為了解決這個問題猜拾,你可以聲明一個 sharedCache,像這樣:
1
2
3
4NSURLCache?*sharedCache?=?[[NSURLCache?alloc]?initWithMemoryCapacity:2?*?1024?*?1024
diskCapacity:100?*?1024?*?1024
diskPath:nil];
[NSURLCache?setSharedURLCache:sharedCache];
這樣佣盒,我們聲明了一個2M內存挎袜,100M磁盤空間的NSURLCache。
對NSURLRequest對象設置緩存策略
NSURLCache對每個NSURLRequest對象都會遵守緩存策略(NSURLRequestCachePolicy)肥惭。策略定義如下:
● NSURLRequestUseProtocolCachePolicy:指定定義在協議實現里的緩存邏輯被用于URL請求盯仪。這是URL請求的默認策略
● NSURLRequestReloadIgnoringLocalCacheData:忽略本地緩存,從源加載
● NSURLRequestReloadIgnoringLocalAndRemoteCacheData:忽略本地&服務器緩存蜜葱,從源加載
● NSURLRequestReturnCacheDataElseLoad:先從緩存加載全景,如果沒有緩存,從源加載
● NSURLRequestReturnCacheDataDontLoad離線模式牵囤,加載緩存數據(無論是否過期)爸黄,不從源加載
● NSURLRequestReloadRevalidatingCacheData存在的緩存數據先確認有效性,無效的話從源加載
用NSURLCache緩存到磁盤
Cache-Control HTTP Header
Cache-Control或者Expires header 必須在從服務器返回的 HTTP response header 中揭鳞,用于客戶端的緩存(Cache-Control header 優(yōu)先權高于 Expires header)炕贵。這里邊有很多需要注意的地方,Cache Control可以有被定義為 max-age的參數(在更新響應之前緩存多長時間)野崇,public/private 訪問称开,或者 no-cache(不緩存響應數據),這里有一個關于HTTP cache headers的文章。
Subclass NSURLCache for Ultimate Control
如果你想繞過 Cache-Control 需求鳖轰,定義你自己的規(guī)則來讀寫一個帶有 NSURLResponse對象的NSURLCache清酥,你可以繼承 NSURLCache。
這里有個例子脆霎,使用 CACHE_EXPIRES 來判斷在獲取源數據之前對緩存數據保留多長時間总处。
(感謝Mattt Thompson的反饋)
50@interface?CustomURLCache?:?NSURLCache
staticNSString?*constCustomURLCacheExpirationKey?=?@"CustomURLCacheExpiration";
staticNSTimeIntervalconstCustomURLCacheExpirationInterval?=?600;
@implementation?CustomURLCache
+?(instancetype)standardURLCache?{
staticCustomURLCache?*_standardURLCache?=?nil;
staticdispatch_once_t?onceToken;
dispatch_once(&onceToken,?^{
_standardURLCache?=?[[CustomURLCache?alloc]
initWithMemoryCapacity:(2?*?1024?*?1024)
diskCapacity:(100?*?1024?*?1024)
diskPath:nil];
}
return_standardURLCache;
}
#pragma?mark?-?NSURLCache
-?(NSCachedURLResponse?*)cachedResponseForRequest:(NSURLRequest?*)request?{
NSCachedURLResponse?*cachedResponse?=?[super?cachedResponseForRequest:request];
if(cachedResponse)?{
NSDate*?cacheDate?=?cachedResponse.userInfo[CustomURLCacheExpirationKey];
NSDate*?cacheExpirationDate?=?[cacheDate?dateByAddingTimeInterval:CustomURLCacheExpirationInterval];
if([cacheExpirationDate?compare:[NSDate?date]]?==?NSOrderedAscending)?{
[self?removeCachedResponseForRequest:request];
returnnil;
}
}
}
returncachedResponse;
}
-?(void)storeCachedResponse:(NSCachedURLResponse?*)cachedResponse
forRequest:(NSURLRequest?*)request
{
NSMutableDictionary?*userInfo?=?[NSMutableDictionary?dictionaryWithDictionary:cachedResponse.userInfo];
userInfo[CustomURLCacheExpirationKey]?=?[NSDate?date];
NSCachedURLResponse?*modifiedCachedResponse?=?[[NSCachedURLResponse?alloc]?initWithResponse:cachedResponse.response?data:cachedResponse.data?userInfo:userInfo?storagePolicy:cachedResponse.storagePolicy];
[super?storeCachedResponse:modifiedCachedResponse?forRequest:request];
}
@end
既然你有了自己的 NSURLCache子類,不要忘了在AppDelegate里邊初始化并使用它
4CustomURLCache?*URLCache?=?[[CustomURLCache?alloc]?initWithMemoryCapacity:2?*?1024?*?1024
diskCapacity:100?*?1024?*?1024
diskPath:nil];
[NSURLCache?setSharedURLCache:URLCache];
Overriding the NSURLResponse before caching
-connection:willCacheResponse代理方法是在被緩存之前用于截斷和編輯由NSURLConnection創(chuàng)建的NSURLCacheResponse的地方睛蛛。為了編輯NSURLCacheResponse鹦马,返回一個可變的拷貝,如下(代碼來自NSHipster blog):
20-?(NSCachedURLResponse?*)connection:(NSURLConnection?*)connection
willCacheResponse:(NSCachedURLResponse?*)cachedResponse?{
NSMutableDictionary?*mutableUserInfo?=?[[cachedResponse?userInfo]?mutableCopy];
NSMutableData?*mutableData?=?[[cachedResponse?data]?mutableCopy];
NSURLCacheStoragePolicy?storagePolicy?=?NSURLCacheStorageAllowedInMemoryOnly;
//?...
return[[NSCachedURLResponse?alloc]?initWithResponse:[cachedResponse?response]
data:mutableData
userInfo:mutableUserInfo
storagePolicy:storagePolicy];
}
//?If?you?do?not?wish?to?cache?the?NSURLCachedResponse,?just?return?nil?from?the?delegate?function:
-?(NSCachedURLResponse?*)connection:(NSURLConnection?*)connection
willCacheResponse:(NSCachedURLResponse?*)cachedResponse?{
returnnil;
}
Disabling NSURLCache
不想使用 NSURLCache,可以忆肾,只需要將內存和磁盤空間容量設為零就可以了
1
2
3
4NSURLCache?*sharedCache?=?[[NSURLCache?alloc]?initWithMemoryCapacity:0
diskCapacity:0
diskPath:nil];
[NSURLCache?setSharedURLCache:sharedCache];
總結
我寫這篇博客是為了iOS社區(qū)貢獻一份力荸频,總結了一下我在處理關于 AFNetworking緩存相關的問題。我們有個內部App加載了好多圖片客冈,導致內存問題以及性能問題旭从。我主要職責就是診斷這個App的緩存行為。在這個研究過程中场仲,我在網上搜索了好多資料并且做了好多調試和悦。然后我總結之后寫到了這篇博客中。我希望這篇文章能夠為其他人用AFNetworking的時候提供幫助渠缕,真心希望對你們有用處鸽素!
外文地址:http://blog.originate.com/blog/2014/02/20/afimagecache-vs-nsurlcache/
來源:tian's blog