SDImageCache
-
[UIScreen mainScreen].scale
開始也以為是屏幕縮放,其實是判斷屏幕分辨率的方法玫坛。
其值為1结笨、2、3時湿镀,分別對應@1x禀梳、@2x、@3x的圖片肠骆。 -
__nullable
與__nonnull
這兩個關(guān)鍵字之前就稍有接觸算途。是蘋果為了兼容OC與Swift混編時加入
的新特性。以區(qū)別是否Swift中的Option蚀腿。具體戳這里:Objective-C新特性__nonnull和__nullable -
SDImageCache單例中維護著一個緩存集合NSCache嘴瓤,用以管理從Disk加載到內(nèi)存的圖片緩存。
簡單介紹請看掘金的NSCache1.NSCache莉钙,與NSMutableDictionary的用法類似廓脆,但它是線程安全的,不需要加線程鎖磁玉。 2.NSCache具有自動刪除的功能停忿,以減少系統(tǒng)占用的內(nèi)存。 3.其對象不會被復制蚊伞,鍵不需要實現(xiàn) NSCopying 協(xié)議席赂。
當收到內(nèi)存警告通知
UIApplicationDidReceiveMemoryWarningNotification
時吮铭,
NSCache清理所有對象。當收到通知
UIApplicationWillTerminateNotification
程序被殺死時颅停,清理沙盒下的緩 存谓晌。當進入后臺時
UIApplicationDidEnterBackgroundNotification
,使用UIBackgroundTaskIdentifier
在后臺再運行一段時間癞揉,來處理過期的緩存圖片纸肉。
-
通過比較圖片Data形式十六進制,前8位Bytes喊熟,判斷圖片為PNG/JPG柏肪。SDWebImage中實現(xiàn)的方 法為
BOOL ImageDataHasPNGPreffix(NSData *data);
// PNG signature bytes and data (below) static unsigned char kPNGSignatureBytes[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
創(chuàng)建一個對象ioQueue名為"com.hackemist.SDWebImageCache"的線程隊列,串行隊列按照FIFO順序執(zhí)行芥牌。一些NSFileManager有關(guān)的操作在這個線程中進行烦味。
-
SDWebImage的圖片在緩存中的默認時間是一個星期。 可自行設置
maxCacheAge
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
-
計算圖片的字節(jié)數(shù)大小胳泉,并設置給NSCache的緩存中拐叉。存儲圖片到Memery的同時會重新轉(zhuǎn)化成Data(判斷JPG或PNG格式),然后使用FileManager存到相應的Disk路徑中扇商。值得注意一下的是
SDCacheCostForImage
if (self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(image); [self.memCache setObject:image forKey:key cost:cost]; }
查詢圖片是否存在于硬盤Disk中凤瘦,Block返回存在的標志,在ioQueue線程中操作案铺。
- (void)diskImageExistsWithKey:(NSString *)key
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
-
SDImageCache查詢圖片的邏輯是先從NSCache中查詢蔬芥,如果有則返回圖片Image,沒有則從Disk中查詢(查詢目錄包括自定義的目錄)控汉,如果有就返回Image并把Image加載到Memory中笔诵。即第7點中所講。
返回Image的過程中還進行了圖片的處理姑子,還原其分辨率格式(@2x乎婿、@3x、gif街佑、webp)谢翎、以及減壓縮
decodedImageWithImage
,而這個提示會有內(nèi)存暴增警告需要注意沐旨。 以下方法理解起來有點小迷糊:
```
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock
//以下為方法中的部分實現(xiàn)
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
return;
}
@autoreleasepool {
//Disk中查詢Image
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
});
```
它返回的是個NSOperation操作森逮,其實是查詢Image的存在方式(緩存中 Or Disk Or Nil),從而策略性的決定是否Image需要在網(wǎng)絡下載磁携。網(wǎng)上說的意思是`從磁盤或者內(nèi)存查詢的過程是異步的褒侧,后面可能需要cancel,所以這樣做`
-
SDImageCache
提供了兩個方法clearDisk
和cleanDisk
,分別是清空整個圖片緩存目錄闷供、清空過期的緩存圖片烟央。
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
//設置要獲取的文件的信息:是否為文件、最后修改日期这吻、全部文件所占的大小
NSArray *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.maxCacheAge];
NSMutableDictionary *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 *urlsToDelete = [[NSMutableArray alloc] init];
for (NSURL *fileURL in fileEnumerator) {
NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];
// Skip directories.
// 如果是目錄就跳過篙议,文件的就操作
if ([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用來繼續(xù)比較時間移怯,刪除最之前的圖片緩存以滿足所設置的緩存需求
[cacheFiles setObject:resourceValues forKey:fileURL];
}
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.
// 計算出的緩存大小不滿足設置的CacheSize,作相應刪除處理
if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
// Target half of our maximum cache size for this cleanup pass.
const NSUInteger desiredCacheSize = self.maxCacheSize / 2;
// Sort the remaining cache files by their last modification time (oldest first).
NSArray *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 *resourceValues = cacheFiles[fileURL];
//刪除文件時繼續(xù)計算當前緩存大小这难,直至滿足預定的大小才Break舟误。
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];
if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
這個刪除緩存圖片的邏輯處理得很巧妙,在實際工作中值得我們借鑒姻乓,
像我們現(xiàn)在的IM中刪除指定時間的聊天記錄等等嵌溢。
- 可以使用
NSDirectoryEnumerator
去檢索沙盒文件,處理計算文件大小蹋岩、文件數(shù)量等赖草。具體使用介紹看蘋果官方API文檔 DirectoryEnumerator