圖片在APP中占有重要的角色,對圖片做好緩存是重要的一項(xiàng)工作。
[TOC]
理論
不喜歡理論的可以直接跳到下面的Demo實(shí)踐部分
緩存介紹
緩存按照保存位置可以分為兩類:內(nèi)存緩存、硬盤緩存(FMDB、CoreData...)。
我們常說的數(shù)據(jù)緩存包含內(nèi)存緩存、硬盤緩存和網(wǎng)絡(luò)請求URL緩存。其中網(wǎng)絡(luò)請求URL緩存也包含內(nèi)存緩存和硬盤緩存。
圖片緩存思路

URL緩存(緩存請求)
網(wǎng)絡(luò)請求除了客戶端需要做簡單的配置外,最主要需要服務(wù)器支持,服務(wù)端也很簡單,只需要在response里面設(shè)置Cache-Control字段就行了.
最常見的URL緩存實(shí)現(xiàn)方式:NSURLCache。NSURLCache可以在memory 和 disk 上緩存。
AFNetWorking是基于NSURLSession(iOS7以上的網(wǎng)絡(luò)請求框架),在生成配置的時(shí)候有三種配置選擇
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
//默認(rèn)會話模式(default):工作模式類似于原來的NSURLConnection,使用的是基于磁盤緩存的持久化策略,使用用戶keychain中保存的證書進(jìn)行認(rèn)證授權(quán)。
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
//瞬時(shí)會話模式(ephemeral):該模式不使用磁盤保存任何數(shù)據(jù)。所有和會話相關(guān)的caches,證書,cookies等都被保存在RAM中,因此當(dāng)程序使會話無效,這些緩存的數(shù)據(jù)就會被自動清空。
+ (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier;
//后臺會話模式(background):該模式在后臺完成上傳和下載,在創(chuàng)建Configuration對象的時(shí)候需要提供一個(gè)NSString類型的ID用于標(biāo)識完成工作的后臺會話。
也就是說default同時(shí)實(shí)現(xiàn)了內(nèi)存緩存和硬盤緩存,ephemeral實(shí)現(xiàn)了內(nèi)存緩存,對于圖片下載我們當(dāng)然選擇default。
我們還可以對緩存的大小進(jìn)行設(shè)置,只需要對NSURLCache進(jìn)行初始化就可以了
實(shí)現(xiàn)初始化
在-application:didFinishLaunchingWithOptions:中對[NSURLCache sharedURLCache]進(jìn)行初始化設(shè)置:
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:20 * 1024 * 1024
diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
也可以單獨(dú)對NSURLSession的configuration進(jìn)行設(shè)置,
在AFNetWorking中對于圖片網(wǎng)絡(luò)請求設(shè)置了20M的內(nèi)存緩存和150M的硬盤緩存:
+ (NSURLCache *)defaultURLCache {
return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
diskCapacity:150 * 1024 * 1024
diskPath:@"com.alamofire.imagedownloader"];
}
緩存策略
緩存策略是指對網(wǎng)絡(luò)請求緩存如果處理,是使用緩存還是不使用
NSURLRequestUseProtocolCachePolicy: 對特定的 URL 請求使用網(wǎng)絡(luò)協(xié)議中實(shí)現(xiàn)的緩存邏輯。這是默認(rèn)的策略。
NSURLRequestReloadIgnoringLocalCacheData:數(shù)據(jù)需要從原始地址加載。不使用現(xiàn)有緩存。
NSURLRequestReloadIgnoringLocalAndRemoteCacheData:不僅忽略本地緩存,
同時(shí)也忽略代理服務(wù)器或其他中間介質(zhì)目前已有的、協(xié)議允許的緩存。
NSURLRequestReturnCacheDataElseLoad:無論緩存是否過期,先使用本地緩存數(shù)據(jù)。
如果緩存中沒有請求所對應(yīng)的數(shù)據(jù),那么從原始地址加載數(shù)據(jù)。
NSURLRequestReturnCacheDataDontLoad:無論緩存是否過期,先使用本地緩存數(shù)據(jù)。
如果緩存中沒有請求所對應(yīng)的數(shù)據(jù),那么放棄從原始地址加載數(shù)據(jù),
請求視為失敗(即:“離線”模式)。
NSURLRequestReloadRevalidatingCacheData:從原始地址確認(rèn)緩存數(shù)據(jù)的合法性后,
緩存數(shù)據(jù)就可以使用,否則從原始地址加載。
在AFNetWorking中同樣對configuration進(jìn)行設(shè)置
configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
如果你繼承AFImageDownloader重新實(shí)現(xiàn)了他的初始化,requestCachePolicy注意AFImageDownloader中只有三種才設(shè)置了緩存
case NSURLRequestUseProtocolCachePolicy:
case NSURLRequestReturnCacheDataElseLoad:
case NSURLRequestReturnCacheDataDontLoad:
圖片內(nèi)存緩存
AFNetWorking3.0放棄了NSCache作為圖片內(nèi)存緩存管理,這讓我非常不解。
有人說它的性能和 key 的相似度有關(guān),如果有大量相似的 key (比如 "1", "2", "3", ...),NSCache 的存取性能會下降得非常厲害,大量的時(shí)間被消耗在 CFStringEqual() 上,不知這是不是放棄使用NSCache的原因。
像素在內(nèi)存中的布局和它在磁盤中的存儲方式并不相同。考慮一種簡單的情況:每個(gè)像素有R、G、B和alpha四個(gè)值,每個(gè)值占用1字節(jié),因此每個(gè)像素占用4字節(jié)的內(nèi)存空間。一張1920*1080的照片(iPhone6 Plus的分辨率)一共有2,073,600個(gè)像素,因此占用了超過8Mb的內(nèi)存。但是一張同樣分辨率的PNG格式或JPEG格式的圖片一般情況下不會有這么大。這是因?yàn)镴PEG將像素?cái)?shù)據(jù)進(jìn)行了一種非常復(fù)雜且可逆的轉(zhuǎn)化。
AFNetWorking3.0的圖片緩存類貌似是基于這個(gè)理論來做內(nèi)存大小管理的(之前AF的內(nèi)存大小計(jì)算方法有錯(cuò),我修改了一下提交了,現(xiàn)在已經(jīng)審核通過合并進(jìn)去了,哈哈哈哈哈,我也算是貢獻(xiàn)過AF了)。AFNetWorking2.x中還是使用AFImageCache進(jìn)行memory上緩存。
NSCache在memory上緩存,類似于NSMutableDictionary ,以 哈希算法 管理。有自動清理機(jī)制,當(dāng)緩存到memory時(shí),如果memory空間不夠,則會自動刪除memory中當(dāng)前界面不使用的空間。
AFAutoPurgingImageCache使用NSMutableDictionary <NSString* , AFCachedImage*>進(jìn)行內(nèi)存緩存映射,并進(jìn)行管理,當(dāng)內(nèi)存警告時(shí)就清空NSMutableDictionary。如果內(nèi)存占用超過限制,則按照時(shí)間順序進(jìn)行刪除。
圖片硬盤緩存
就是我們常說的把數(shù)據(jù)保存在本地,比如FMDB、CoreData、歸檔、NSUserDefaults、NSFileManager等等,這里就不多說了。
AFNetWorking3.0沒有直接做圖片硬盤緩存,而是通過URL緩存做的硬盤緩存。也就是說,如果內(nèi)存緩存沒有讀取到圖片,就會調(diào)用下載邏輯,通過下載緩存的內(nèi)存緩存硬盤緩存來獲取到已下載過的圖片,如果沒有下載過,就會重新下載。
如果我們自己做圖片硬盤緩存建議使用NSFileManager,因?yàn)橐话銏D片data會比較大,測試證明路徑緩存會比放在數(shù)據(jù)庫有更高的性能。
實(shí)踐
使用NSURLSession做網(wǎng)絡(luò)請求緩存。
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; //使用default配置,自帶網(wǎng)絡(luò)請求緩存
[config setHTTPAdditionalHeaders:@{@"Accept":@"image/*"}];//設(shè)置網(wǎng)絡(luò)數(shù)據(jù)格式
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
WEAKSELF
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData * _Nullable data,
NSURLResponse * _Nullable response, NSError * _Nullable error) {
//使用’獲取數(shù)據(jù)(NSURLSessionDataTask)‘的方式發(fā)起請求
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.imageView.image = image;
});
}];
[task resume];
我們通過連續(xù)兩次下載圖片可以發(fā)現(xiàn)defaultSessionConfiguration下NSURLSession會自動做網(wǎng)絡(luò)請求緩存。
使用AFNetWorking下載圖片
導(dǎo)入頭文件#import "UIImageView+AFNetworking.h"
使用特別簡單,只有一行代碼:[imageView setImageWithURL:url];
UIImageView+AFNetworking做了內(nèi)存緩存和基于NSURLSession的網(wǎng)絡(luò)請求緩存,并沒有做硬盤緩存,估計(jì)是考慮到圖片的網(wǎng)絡(luò)請求硬盤緩存足以滿足需要,所以省略了額外的硬盤緩存,內(nèi)存緩存加快了讀取速度,這個(gè)是非常有必要的。
進(jìn)入U(xiǎn)IImageView+AFNetworking代碼分析:
if ([urlRequest URL] == nil) {
[self cancelImageDownloadTask];
self.image = placeholderImage;
return;
}
//如果新傳入的URL為空則取消圖片下載并設(shè)置圖片為默認(rèn)圖
if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
return;
}
//如果新傳入的URL與當(dāng)前URL相同則直接返回,否則取消當(dāng)前下載,重新進(jìn)行圖片查找下載
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
//從內(nèi)存緩存中讀取image,如果沒有則發(fā)起新的請求
AFImageDownloader *downloader = [[self class] sharedImageDownloader];
//使用單例下載,內(nèi)存緩存為downloader.imageCache
//downloader設(shè)置的網(wǎng)絡(luò)請求20M的內(nèi)存緩存和150M的硬盤緩存
//downloader設(shè)置的網(wǎng)絡(luò)請求緩存策略為NSURLRequestUseProtocolCachePolicy
//imageCache設(shè)置了內(nèi)存60M最大100M
//網(wǎng)絡(luò)請求發(fā)起前會再次判斷imageCache中是否含有該image
測試
使用Charles查看圖片下載的網(wǎng)絡(luò)請求發(fā)生了幾次,判斷緩存是否成功。
其中硬盤緩存需要寫入時(shí)間,網(wǎng)絡(luò)請求完成后略等一下,否則硬盤緩存不會生效
設(shè)置默認(rèn)網(wǎng)絡(luò)緩存大小
如果沒有對NSURLRequest的URLCache進(jìn)行設(shè)置,默認(rèn)是使用[NSURLCache sharedURLCache],所以如果有需要可以如下設(shè)置
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
//網(wǎng)絡(luò)請求時(shí)狀態(tài)欄網(wǎng)絡(luò)狀態(tài)小轉(zhuǎn)輪
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:20 * 1024 * 1024
diskPath:nil];
//內(nèi)存4M,硬盤20M
[NSURLCache setSharedURLCache:URLCache];
return YES;
}