前言
使用SDWebImage為我們帶來(lái)的另一個(gè)方便就是它提供圖片的緩存功能桥爽,自動(dòng)將下載好的圖片緩存到本地朱灿,防止重復(fù)下載。本篇文章主要學(xué)習(xí)的就是SDWebImage的緩存功能是怎么實(shí)現(xiàn)的钠四。
Cache
提供緩存相關(guān)的文件有2個(gè)盗扒,分別是SDImageCacheConfig
,SDImageCache
。
- SDImageCacheConfig:提供緩存操作的配置屬性
//是否解壓下載的圖片缀去,默認(rèn)是YES,但是會(huì)消耗掉很多內(nèi)存侣灶,如果遇到內(nèi)存不足的crash時(shí),將值設(shè)為NO
@property (assign, nonatomic) BOOL shouldDecompressImages;
//允許自動(dòng)上傳的iCloud
@property (assign, nonatomic) BOOL shouldDisableiCloud;
//允許緩存到內(nèi)存
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
//讀取磁盤緩存的策略
@property (assign, nonatomic) NSDataReadingOptions diskCacheReadingOptions;
//最大緩存時(shí)間
@property (assign, nonatomic) NSInteger maxCacheAge;
//最大緩存大小
@property (assign, nonatomic) NSUInteger maxCacheSize;
- SDImageCache:實(shí)現(xiàn)主要的緩存邏輯缕碎,緩存分為內(nèi)存緩存
memCache
和磁盤緩存fileManager
兩部分褥影,并提供存儲(chǔ),查詢咏雌,讀取凡怎,刪除相關(guān)操作。
內(nèi)存緩存
內(nèi)存緩存是使用NSCache
實(shí)現(xiàn)的赊抖,NSCache
使用上類似字典统倒,可以用key-Value的方式存取數(shù)據(jù)。但是NSCache
底層實(shí)現(xiàn)和NSDictionary
不同(NSCache是線程安全的)氛雪。
@interface AutoPurgeCache : NSCache
@end
@implementation AutoPurgeCache
- (nonnull instancetype)init {
self = [super init];
if (self) {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
可以看到檐薯,里面實(shí)現(xiàn)了一個(gè)NSCache的子類。添加了一個(gè)觀察者注暗,當(dāng)收到內(nèi)存警告的時(shí)候坛缕,移除所有的緩存。
磁盤緩存
磁盤緩存使用NSFileManager
實(shí)現(xiàn)捆昏,通過(guò)一個(gè)串行隊(duì)列進(jìn)行異步任務(wù)管理赚楚。并要求所有寫操作都必須放在這個(gè)隊(duì)列中執(zhí)行
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
通過(guò)標(biāo)簽檢測(cè)隊(duì)列是不是ioQueue
- (void)checkIfQueueIsIOQueue {
const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
NSLog(@"This method should be called from the ioQueue");
}
}
核心緩存方法是- (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock
通過(guò)這個(gè)方法將圖片進(jìn)行內(nèi)存緩存或磁盤緩存。
//檢測(cè)正確性
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
//如果需要進(jìn)行內(nèi)存緩存骗卜,則將圖片直接緩存進(jìn)NSCache中
if (self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}
//如果需要磁盤緩存
if (toDisk) {
//在串行隊(duì)列中異步執(zhí)行緩存操作
dispatch_async(self.ioQueue, ^{
//大量對(duì)象生成釋放宠页,使用autoreleasepool控制內(nèi)存
@autoreleasepool {
NSData *data = imageData;
if (!data && image) {
// 圖片二進(jìn)制數(shù)據(jù)不存在左胞,重新生成,首先獲取圖片的格式举户,然后使用`UIImagePNGRepresentation`或者`UIImageJPEGRepresentation`生成圖片烤宙,之后會(huì)查看NSData的這些分類方法
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:SDImageFormatPNG];
}
// 磁盤緩存核心方法
[self storeImageDataToDisk:data forKey:key];
}
if (completionBlock) {
// 主線程回調(diào)
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
通過(guò)- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key
方法將數(shù)據(jù)寫入進(jìn)磁盤
//正確性校驗(yàn)
if (!imageData || !key) {
return;
}
//驗(yàn)證當(dāng)前隊(duì)列是否正確
[self checkIfQueueIsIOQueue];
//檢測(cè)磁盤中是否已存在該文件,如果不存在則創(chuàng)建這個(gè)目錄
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
//創(chuàng)建文件名俭嘁,并轉(zhuǎn)化為URL格式
NSString *cachePathForKey = [self defaultCachePathForKey:key];
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
//存儲(chǔ)圖片到該路徑
[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
// 如果需要躺枕,上傳到iCloud
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
緩存查詢
核心方法- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock
,當(dāng)查詢圖片時(shí)供填,該操作會(huì)在內(nèi)存中放置一份緩存拐云,如果確定需要緩存到磁盤,則將磁盤緩存操作作為一個(gè)task放到串行隊(duì)列中處理近她。
//正確性校驗(yàn)
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// 檢測(cè)內(nèi)存中是否已緩存該圖片
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
NSData *diskData = nil;
// isGIF單獨(dú)處理
if (image.images) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
// 查詢完成叉瘩,是內(nèi)存緩存,查詢操作不需要在io隊(duì)列中執(zhí)行
if (doneBlock) {
doneBlock(image, diskData, SDImageCacheTypeMemory);
}
return nil;
}
//內(nèi)存上沒(méi)有粘捎,創(chuàng)建一個(gè)任務(wù)
NSOperation *operation = [NSOperation new];
//在io隊(duì)列執(zhí)行
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
// 檢測(cè)任務(wù)狀態(tài)
return;
}
@autoreleasepool {
//及時(shí)釋放內(nèi)存
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
//如果需要緩存到內(nèi)存薇缅,則把讀取出來(lái)的數(shù)據(jù)拷貝一份到內(nèi)存中
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
//回調(diào)
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
//返回該任務(wù)給調(diào)度層
return operation;
移除緩存
可以通過(guò)removeImageForKey
方法移除指定的緩存。這個(gè)操作也是異步的攒磨,需要放在ioQueue中執(zhí)行泳桦。
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
if (key == nil) {
return;
}
if (self.config.shouldCacheImagesInMemory) {
[self.memCache removeObjectForKey:key];
}
if (fromDisk) {
dispatch_async(self.ioQueue, ^{
[_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
} else if (completion){
completion();
}
}
除此之外,SDWebImage還支持批量移除咧纠,可以根據(jù)配置的緩存時(shí)間蓬痒,緩存大小等參數(shù)管理緩存數(shù)據(jù)的聲明周期。
使用deleteOldFilesWithCompletionBlock
刪除時(shí)間太久的數(shù)據(jù)
使用clearMemory
清除NSCache中的數(shù)據(jù)
使用clearDiskOnCompletion
清除所有磁盤上的數(shù)據(jù)漆羔,這個(gè)函數(shù)會(huì)刪除創(chuàng)建的目錄結(jié)構(gòu)
小結(jié)
SDWebImage緩存主要分為內(nèi)存緩存和磁盤緩存兩部分梧奢,內(nèi)存緩存使用NSCache進(jìn)行緩存,在內(nèi)存占用過(guò)多時(shí)可以釋放多余內(nèi)存演痒。磁盤緩存使用NSFileManager實(shí)現(xiàn)亲轨,使用了一個(gè)串行隊(duì)列來(lái)保證操作的正確性,異步執(zhí)行讀寫操作保證不影響主線程鸟顺。提供配置項(xiàng)管理緩存策略讓內(nèi)存緩存和磁盤緩存協(xié)調(diào)工作惦蚊。防止了冗余的緩存操作。
相關(guān)文章
SDWebImage源碼閱讀筆記
SDWebImage源碼閱讀
SDWebImage 源碼閱讀筆記(二)
SDWebImage源碼閱讀筆記