第六篇
前言
我們在SDWebImageCache(上)中了解了這個緩存類大概的功能是什么?那么接下來就要看看這些功能是如何實(shí)現(xiàn)的? 再次強(qiáng)調(diào),不管是圖片的緩存還是其他各種不同形式的緩存,在原理上都極其相似,我們通過SDWebImageCache,來看看作者是如何實(shí)現(xiàn)這個功能的。
在業(yè)務(wù)中,經(jīng)常要緩存數(shù)據(jù),通過本篇的學(xué)習(xí),我們寫出的緩存管理者這個管理者對象,就能夠有所進(jìn)步。
NSCache
對于很多開發(fā)者來說,NSCache是一個陌生人,因?yàn)榇蠹彝鶎SMutableDictionary情有獨(dú)鐘。可憐的 NSCache 一直處于 NSMutableDictionary 的陰影之下。就好像沒有人知道它提供了垃圾處理的功能,而開發(fā)者們卻費(fèi)勁力氣地去自己實(shí)現(xiàn)它。
沒錯,NSCache 基本上就是一個會自動移除對象來釋放內(nèi)存的 NSMutableDictionary。無需響應(yīng)內(nèi)存警告或者使用計(jì)時器來清除緩存。唯一的不同之處是鍵對象不會像 NSMutableDictionary 中那樣被復(fù)制,這實(shí)際上是它的一個優(yōu)點(diǎn)(鍵不需要實(shí)現(xiàn) NSCopying 協(xié)議)。
當(dāng)有緩存數(shù)據(jù)到內(nèi)存的業(yè)務(wù)的時候,就應(yīng)該考慮NSCache了,有緩存就有清楚緩存。
NSCache 每個方法和屬性的具體作用,請參考這篇文章NSCache
AutoPurgeCache
NSCache在收到內(nèi)存警告的時候會釋放自身的一部分資源,設(shè)計(jì)AutoPurgeCache的目的是在收到警告時,釋放緩存的所有資源。
通過繼承自NSCache,監(jiān)聽UIApplicationDidReceiveMemoryWarningNotification來實(shí)現(xiàn)。
@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;
}
- (void)dealloc {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
@end
計(jì)算一個UIImage的SDCacheCost
圖片在該緩存中的大小是通過像素來衡量的。
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
return image.size.height * image.size.width * image.scale * image.scale;
#endif
}
** 注意:FOUNDATION_STATIC_INLINE表示該函數(shù)是一個具有文件內(nèi)部訪問權(quán)限的內(nèi)聯(lián)函數(shù),所謂的內(nèi)聯(lián)函數(shù)就是建議編譯器在調(diào)用時將函數(shù)展開。建議的意思就是說編譯器不一定會按照你的建議做。因此內(nèi)聯(lián)函數(shù)盡量不要寫的太復(fù)雜。**
Properties
SDWebImageCache實(shí)現(xiàn)部分有下邊幾個屬性:
#pragma mark - Properties
@property (strong, nonatomic, nonnull) NSCache *memCache;
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t ioQueue;
@end
@implementation SDImageCache {
NSFileManager *_fileManager;
}
-
memCache內(nèi)存容器 -
diskCachePath硬盤緩存路徑 -
customPaths自定義的讀取路徑,這是一個數(shù)組,我們可以通過addReadOnlyCachePath:這個方法往里邊添加路徑。當(dāng)我們讀取讀片的時候,這個數(shù)組的路徑也會作為數(shù)據(jù)源 -
ioQueue稱作輸入輸出隊(duì)列,隊(duì)列往往可以當(dāng)做一種“鎖”來使用,我們把某些任務(wù)按照順利一步一步的進(jìn)行,必須考慮線程是否安全 -
_fileManager文件管理者,這個就不多說了,大家都知道怎么用
初始化
這一部分關(guān)系到Singleton, init, dealloc這三個方面的代碼,初始化有四個方法,我們重點(diǎn)講解最后一個初始化方法(這也是作者建議的方法,其他方法通過該方法實(shí)現(xiàn)):
+ (nonnull instancetype)sharedImageCache
- (instancetype)init
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory {
if ((self = [super init])) {
NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
// Create IO serial queue
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
_config = [[SDImageCacheConfig alloc] init];
// Init the memory cache
_memCache = [[AutoPurgeCache alloc] init];
_memCache.name = fullNamespace;
// Init the disk cache
if (directory != nil) {
_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
} else {
NSString *path = [self makeDiskCachePath:ns];
_diskCachePath = path;
}
dispatch_sync(_ioQueue, ^{
_fileManager = [NSFileManager new];
});
#if SD_UIKIT
// Subscribe to app events
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deleteOldFiles)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#endif
}
return self;
}
這個方法其實(shí)就做了兩件事:1.初始化自身的屬性 2.添加通知監(jiān)聽。其他的初始化代碼在這里就不寫了。
Cache paths
1.添加自定義路徑
- (void)addReadOnlyCachePath:(nonnull NSString *)path {
if (!self.customPaths) {
self.customPaths = [NSMutableArray new];
}
if (![self.customPaths containsObject:path]) {
[self.customPaths addObject:path];
}
}
2.文件名(MD5)
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
const char *str = key.UTF8String;
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];
return filename;
}
3.默認(rèn)的某個圖片的路徑
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
return [self cachePathForKey:key inPath:self.diskCachePath];
}
4.根據(jù)名稱和路徑拼接路徑
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
NSString *filename = [self cachedFileNameForKey:key];
return [path stringByAppendingPathComponent:filename];
}
Store Image
保存圖片也有四個方法,我們按照順序來看:
1.保存數(shù)據(jù)到Disk
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
if (!imageData || !key) {
return;
}
[self checkIfQueueIsIOQueue];
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// get cache Path for image key
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// transform to NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
// disable iCloud backup
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
- 檢查imageData或者key是否為nil
- 檢查是否在自身的隊(duì)列中進(jìn)行的操作
- 創(chuàng)建Disk緩存文件夾
- 根據(jù)key獲取默認(rèn)的緩存路徑
- 將數(shù)據(jù)寫入到上邊獲取的路徑中
- 根據(jù)配置文件設(shè)置是否禁用iCloud的備份功能
2.參數(shù)最多的保存圖片的方法
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled
if (self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}
if (toDisk) {
dispatch_async(self.ioQueue, ^{
NSData *data = imageData;
if (!data && image) {
SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
data = [image sd_imageDataAsFormat:imageFormatFromData];
}
[self storeImageDataToDisk:data forKey:key];
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
}
- 檢查image或者key是否為nil
- 根據(jù)配置文件中是否設(shè)置了緩存到內(nèi)存,保存image到緩存中,這個過程是非??斓?,因此不用考慮線程
- 如果保存到Disk,創(chuàng)建異步串行隊(duì)列 我們把數(shù)據(jù)保存到Disk,其實(shí)保存的應(yīng)該是數(shù)據(jù)的二進(jìn)制文件
- 保存二進(jìn)制數(shù)據(jù)到Disk,如果不存在,需要把image轉(zhuǎn)換成NSData
- 調(diào)用Block
3.其他兩個保存的方法
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock];
}
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
}
Query and Retrieve 數(shù)據(jù)
1.根據(jù)key判斷Disk中的數(shù)據(jù)是否存在
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
dispatch_async(_ioQueue, ^{
BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
if (!exists) {
exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(exists);
});
}
});
}
2.獲取緩存到內(nèi)存中的數(shù)據(jù)
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memCache objectForKey:key];
}
3.獲取Disk中的數(shù)據(jù)
在Disk中獲取數(shù)據(jù)跟在內(nèi)存中獲取不一樣,內(nèi)存中直接保存的是UIImage,而Disk中保存的是NSData,因此肯定需要一個NSData -> UIImage 的轉(zhuǎn)換過程。接下來我們看看這個轉(zhuǎn)換過程:
-
根據(jù)key獲取Disk中的NSData數(shù)據(jù),總體思路就是先從默認(rèn)的路徑獲取,如果沒有獲取到,再從自定義的路徑獲取,值得注意的是,要考慮沒有pathExtention的情況
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key { NSString *defaultPath = [self defaultCachePathForKey:key]; NSData *data = [NSData dataWithContentsOfFile:defaultPath]; if (data) { return data; } // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // checking the key with and without the extension data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension]; if (data) { return data; } NSArray<NSString *> *customPaths = [self.customPaths copy]; for (NSString *path in customPaths) { NSString *filePath = [self cachePathForKey:key inPath:path]; NSData *imageData = [NSData dataWithContentsOfFile:filePath]; if (imageData) { return imageData; } // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // checking the key with and without the extension imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension]; if (imageData) { return imageData; } } return nil; } -
根據(jù)NSData 獲取 UIImage,需要scaled圖片,根據(jù)配置文件的設(shè)置,是否解壓圖片
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key { NSData *data = [self diskImageDataBySearchingAllPathsForKey:key]; if (data) { UIImage *image = [UIImage sd_imageWithData:data]; image = [self scaledImageForKey:key image:image]; if (self.config.shouldDecompressImages) { image = [UIImage decodedImageWithImage:image]; } return image; } else { return nil; } } -
將UIImage 放入內(nèi)存,返回圖片
- (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; }
4.先獲取內(nèi)存的數(shù)據(jù),如果沒有,在獲取Disk的數(shù)據(jù)
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
return image;
}
// Second check the disk cache...
image = [self imageFromDiskCacheForKey:key];
return image;
}
5.異步獲取數(shù)據(jù)
上邊1.2.3.4 中獲取數(shù)據(jù)的方法都不是異步獲取的,在SDWebImageCache中,涉及到異步獲取的,都會通過Block來回調(diào)的。
這個異步獲取值得說的有兩點(diǎn):
- 如果在內(nèi)存中獲取到的圖片是GIF,那么要去Disk中獲取
- 為什么要返回一個NSOperation對象呢? 其實(shí)我們可以通過這個NSOperation對象取消獲取任務(wù)
代碼:
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
NSData *diskData = nil;
if ([image isGIF]) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
if (doneBlock) {
doneBlock(image, diskData, SDImageCacheTypeMemory);
}
return nil;
}
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
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(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
return operation;
}
Remove 數(shù)據(jù)
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion {
[self removeImageForKey:key fromDisk:YES withCompletion:completion];
}
- (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();
}
}
Mem Cache settings
- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
self.memCache.totalCostLimit = maxMemoryCost;
}
- (NSUInteger)maxMemoryCost {
return self.memCache.totalCostLimit;
}
- (NSUInteger)maxMemoryCountLimit {
return self.memCache.countLimit;
}
- (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit {
self.memCache.countLimit = maxCountLimit;
}
清空數(shù)據(jù)
清空數(shù)據(jù)有值得我們注意的地方,我們一個一個方法的看:
1.清空內(nèi)存緩存數(shù)據(jù)
- (void)clearMemory {
[self.memCache removeAllObjects];
}
2.清空Disk數(shù)據(jù)
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
dispatch_async(self.ioQueue, ^{
[_fileManager removeItemAtPath:self.diskCachePath error:nil];
[_fileManager createDirectoryAtPath:self.diskCachePath
withIntermediateDirectories:YES
attributes:nil
error:NULL];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
}
3.清空舊數(shù)據(jù)
對于清空舊數(shù)據(jù)而言,我們需要考慮兩個方面:
- 首先要清空掉所有的過期的數(shù)據(jù)
- 過期的數(shù)據(jù)清空后,緩存的數(shù)據(jù)比我們設(shè)置的最大緩存量還大,我們要繼續(xù)清空數(shù)據(jù),直到滿足我們的需求為止
這里邊大概用到的思路就是上邊說的兩點(diǎn),關(guān)鍵是如何實(shí)現(xiàn)上邊所說的內(nèi)容。有一些我們平時可能不太接觸的知識點(diǎn),在這里做一些簡要的講解
首先我們需要遍歷Disk緩存路徑下的所有文件,那么我們怎么遍歷呢?NSFileManager有一個很好地方法:

返回一個
NSDirectoryEnumerator<NSURL *> *這個對象中存放的是NSURLsurl 需要遍歷的路徑
-
(nullable NSArray<NSURLResourceKey> \*)keys這個需要傳入一個數(shù)組,表示想獲取的NSURLResourceKeys,我們來看看這個NSURLResourceKey: 點(diǎn)進(jìn)去看了下。太長了,在這里就不copy了。有興趣的同學(xué),自己去看看,太長了。我們就說說著這個清空方法中用到的把:- NSURLIsDirectoryKey 是否是文件夾
- NSURLContentModificationDateKey 最后修改時間
- NSURLTotalFileAllocatedSizeKey 分配的尺寸
options:(NSDirectoryEnumerationOptions)mask 傳入過濾參數(shù),這里NSDirectoryEnumerationSkipsHiddenFiles 是指忽略隱藏文件
其次,我們有了這些參數(shù)了。在根據(jù)最后修改日期是否過期,刪除掉過期的數(shù)據(jù)就行了。還有一個值得我們注意的就是如何對一個字典進(jìn)行排序:
NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}];
這個會返回排好序的字典的所有的key。NSSortConcurrent是并發(fā)排序,效率高,但可能不穩(wěn)定,NSSortStable 穩(wěn)定,但可能效率不如NSSortConcurrent高。排序的規(guī)則通過Block指定。
好了,基本要注意的就這些。這個函數(shù)的實(shí)現(xiàn)也是基于這種思路。
- (void)deleteOldFiles {
[self deleteOldFilesWithCompletionBlock:nil];
}
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
// This enumerator prefetches useful properties for our cache files.
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
NSUInteger currentCacheSize = 0;
// Enumerate all of the files in the cache directory. This loop has two purposes:
//
// 1. Removing files that are older than the expiration date.
// 2. Storing file attributes for the size-based cleanup pass.
NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
for (NSURL *fileURL in fileEnumerator) {
NSError *error;
NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
// Skip directories and errors.
if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue;
}
// Remove files that are older than the expiration date;
NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL];
continue;
}
// Store a reference to this file and account for its total size.
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
cacheFiles[fileURL] = resourceValues;
}
for (NSURL *fileURL in urlsToDelete) {
[_fileManager removeItemAtURL:fileURL error:nil];
}
// If our remaining disk cache exceeds a configured maximum size, perform a second
// size-based cleanup pass. We delete the oldest files first.
if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
// Target half of our maximum cache size for this cleanup pass.
const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;
// Sort the remaining cache files by their last modification time (oldest first).
NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}];
// Delete files until we fall below our desired cache size.
for (NSURL *fileURL in sortedFiles) {
if ([_fileManager removeItemAtURL:fileURL error:nil]) {
NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
申請一段時間在后臺刪除舊數(shù)據(jù)
- (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;
}];
}
Cache Info
- (NSUInteger)getSize {
__block NSUInteger size = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
for (NSString *fileName in fileEnumerator) {
NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
NSDictionary<NSString *, id> *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
size += [attrs fileSize];
}
});
return size;
}
- (NSUInteger)getDiskCount {
__block NSUInteger count = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
count = fileEnumerator.allObjects.count;
});
return count;
}
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock {
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
dispatch_async(self.ioQueue, ^{
NSUInteger fileCount = 0;
NSUInteger totalSize = 0;
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:@[NSFileSize]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
for (NSURL *fileURL in fileEnumerator) {
NSNumber *fileSize;
[fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
totalSize += fileSize.unsignedIntegerValue;
fileCount += 1;
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(fileCount, totalSize);
});
}
});
}
總結(jié)
SDWebImageCache 就寫完了,本篇算是長文了,對于那種圖片比較多的app,實(shí)現(xiàn)一個自己的緩存類還是很有必要的。
由于個人知識有限,如有錯誤之處,還望各路大俠給予指出啊