SDImageCache
-
[UIScreen mainScreen].scale
开始也以为是屏幕缩放,其实是判断屏幕分辨率的方法。
其值为1、2、3时,分别对应@1x、@2x、@3x的图片。 -
__nullable
与__nonnull
这两个关键字之前就稍有接触。是苹果为了兼容OC与Swift混编时加入
的新特性。以区别是否Swift中的Option。具体戳这里:Objective-C新特性__nonnull和__nullable -
SDImageCache单例中维护着一个缓存集合NSCache,用以管理从Disk加载到内存的图片缓存。
简单介绍请看掘金的NSCache1.NSCache,与NSMutableDictionary的用法类似,但它是线程安全的,不需要加线程锁。 2.NSCache具有自动删除的功能,以减少系统占用的内存。 3.其对象不会被复制,键不需要实现 NSCopying 协议。
当收到内存警告通知
UIApplicationDidReceiveMemoryWarningNotification
时,
NSCache清理所有对象。当收到通知
UIApplicationWillTerminateNotification
程序被杀死时,清理沙盒下的缓 存。当进入后台时
UIApplicationDidEnterBackgroundNotification
,使用UIBackgroundTaskIdentifier
在后台再运行一段时间,来处理过期的缓存图片。
-
通过比较图片Data形式十六进制,前8位Bytes,判断图片为PNG/JPG。SDWebImage中实现的方 法为
BOOL ImageDataHasPNGPreffix(NSData *data);
// PNG signature bytes and data (below) static unsigned char kPNGSignatureBytes[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
创建一个对象ioQueue名为"com.hackemist.SDWebImageCache"的线程队列,串行队列按照FIFO顺序执行。一些NSFileManager有关的操作在这个线程中进行。
-
SDWebImage的图片在缓存中的默认时间是一个星期。 可自行设置
maxCacheAge
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
-
计算图片的字节数大小,并设置给NSCache的缓存中。存储图片到Memery的同时会重新转化成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
,而这个提示会有内存暴增警告需要注意。 以下方法理解起来有点小迷糊:
```
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock
//以下为方法中的部分实现
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需要在网络下载。网上说的意思是`从磁盘或者内存查询的过程是异步的,后面可能需要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用来继续比较时间,删除最之前的图片缓存以满足所设置的缓存需求
[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];
//删除文件时继续计算当前缓存大小,直至满足预定的大小才Break。
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];
if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
这个删除缓存图片的逻辑处理得很巧妙,在实际工作中值得我们借鉴,
像我们现在的IM中删除指定时间的聊天记录等等。
- 可以使用
NSDirectoryEnumerator
去检索沙盒文件,处理计算文件大小、文件数量等。具体使用介绍看苹果官方API文档 DirectoryEnumerator