SDImageCache
是SDWebImage庫的圖片緩存類粒梦,其提供了內存和磁盤緩存兩種機制,并且設計了一些策略對緩存的圖片進行管理钾腺。
以下代碼和分析都基于041842bf085cbb711f0d9e099e6acbf6fd533b0c這個commit瞻离。
SDImageCache
// See https://github.com/rs/SDWebImage/pull/1141 for discussion
@interface AutoPurgeCache : NSCache
@end
@implementation AutoPurgeCache
- (nonnull instancetype)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
@end
AutoPurgeCache
用作SDImageCache
的內存緩存使用。雖然NSCache
本身為了保證不引起內存問題力惯,實現(xiàn)了自動剔除數(shù)據(jù)的策略,但它仍然無法保證在UIApplicationDidReceiveMemoryWarningNotification
發(fā)生時清空內存召嘶,詳見這里父晶。
所以AutoPurgeCache
在收到UIApplicationDidReceiveMemoryWarningNotification
后,調用removeAllObjects
清除緩存里所有的數(shù)據(jù)弄跌。
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
return image.size.height * image.size.width * image.scale * image.scale;
}
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
...
if (self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}
...
}
- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
self.memCache.totalCostLimit = maxMemoryCost;
}
NSCache
支持設置totalCostLimit
甲喝,在設置每一個數(shù)據(jù)的時候都分別設上數(shù)據(jù)的cost。緩存里的數(shù)據(jù)到達限制時會觸發(fā)NSCache
的清理策略铛只,來保證數(shù)據(jù)的總cost低于限制埠胖。清理數(shù)據(jù)的具體時機和策略由NSCache
內部實現(xiàn)決定,外部無法控制淳玩。
cost一般被設置為value的占用字節(jié)數(shù)直撤,但是也可以根據(jù)需求設計不同的邏輯。
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}
最近命中的數(shù)據(jù)有較大可能會再次被用到蜕着,所以在從磁盤讀取數(shù)據(jù)后谋竖,把它也存到內存緩存。
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory {
...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
...
}
- (void)backgroundDeleteOldFiles {
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
[self deleteOldFilesWithCompletionBlock:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
在程序進到后臺以后,開一個后臺任務清理圖片緩存蓖乘,把過期的圖片緩存從磁盤上清除锤悄。
清除策略保存在SDImageCacheConfig
里,包括了過期時間和最大占用磁盤空間兩項驱敲。
具體的實現(xiàn)是先刪除所有過期的圖片铁蹈,再從舊到新刪除文件,直到滿足磁盤空間要求众眨。詳細代碼見deleteOldFilesWithCompletionBlock
方法握牧。
SDImageCacheTests
// Testing storeImage:forKey:toDisk:YES
- (void)test07InsertionOfImageForcingDiskStorage{
XCTestExpectation *expectation = [self expectationWithDescription:@"storeImage forKey toDisk=YES"];
UIImage *image = [self imageForTesting];
[self.sharedImageCache storeImage:image forKey:kImageTestKey toDisk:YES completion:nil];
expect([self.sharedImageCache imageFromMemoryCacheForKey:kImageTestKey]).to.equal(image);
[self.sharedImageCache diskImageExistsWithKey:kImageTestKey completion:^(BOOL isInCache) {
if (isInCache) {
[expectation fulfill];
} else {
XCTFail(@"Image should be in cache");
}
}];
[self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
}
緩存類的測試代碼可以直接使用類本身提供的增刪改查接口進行相互驗證,這段代碼中也包括了XCTest框架中異步接口的測試寫法娩梨。
SDWebImage源碼系列
SDWebImage源碼之SDImageCache
SDWebImage源碼之SDWebImageDownloader
SDWebImage源碼之SDWebImageManager