SDWebImage源码解析
SDWebImage中核心类如下所示:
26E06FD9-3B94-4AB4-9375-A2E7E5CC4470副本.png一、SDImageCache
SDImageCache负责缓存图片,包含几个对外属性
//是否解压图片
@property (assign, nonatomic) BOOL shouldDecompressImages;
//iCloud 备份
@property (assign, nonatomic) BOOL shouldDisableiCloud;
//是否能够缓存图片到内存
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
//最大内存使用,内存
@property (assign, nonatomic) NSUInteger maxMemoryCost;
//缓存对象的数量最大值
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;
//缓存时间,默认一周
@property (assign, nonatomic) NSInteger maxCacheAge;
//缓存最大值,磁盘
@property (assign, nonatomic) NSUInteger maxCacheSize;
内部属性:
//缓存对象
@property (strong, nonatomic) NSCache *memCache;
//磁盘缓存路径
@property (strong, nonatomic) NSString *diskCachePath;
//自定义路径数组,如bundle里的图片,可以添加到该数组中
@property (strong, nonatomic) NSMutableArray *customPaths;
//读写队列
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;
AutoPurgeCache,当收到内存警告时自动清空缓存
//初始化类的成员变量,监听内存警告、APP退到后台、APP终止通知
- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory
//根据key值、path值生成文件路径 - (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path
//根据key值得到文件名 - (NSString *)cachedFileNameForKey:(NSString )key
//获取缓存到磁盘的路径
-(NSString )makeDiskCachePath:(NSString)fullNamespace
/存储图片到磁盘
*recalculate表示是否需要把image类型转换为nsdata类型,如果不需要转换则把imageData直接写入磁盘,如果需要转换则把image转换为data
*/
<pre><code>
-
(void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk
{if (!image || !key) {
return;
}if (self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];//把图片存储到缓存
}if (toDisk) {
dispatch_async(self.ioQueue, ^{
NSData *data = imageData;
// image非空才能转换为data
if (image && (recalculate || !data)) {
if TARGET_OS_IPHONE
//获取图片的alpha信息,判断是否是png图片
int alphaInfo = CGImageGetAlphaInfo(image.CGImage);
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
BOOL imageIsPng = hasAlpha;
if ([imageData length] >= [kPNGSignatureData length]) {
imageIsPng = ImageDataHasPNGPreffix(imageData);
}
if (imageIsPng) {
data = UIImagePNGRepresentation(image);
}
else {
data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
}
else
data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
endif
}
// 把data写入磁盘
if (data) {
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:data attributes:nil];
// disable iCloud backup
if (self.shouldDisableiCloud) {
[fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
});
}
}
</code></pre>
//查询图片是否已存在
- (BOOL)diskImageExistsWithKey:(NSString *)key;
- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//从缓存中取图片
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key
//从磁盘中取图片,先从内存缓存中取,无则从磁盘中取 - (UIImage *)imageFromDiskCacheForKey:(NSString *)key
//根据key生成文件路径,先从默认图片路径中查找,无则从自定义路径中查找,返回image的data数据 - (NSData *)diskImageDataBySearchingAllPathsForKey:(NSString *)key;
//根据key找到image - (UIImage *)diskImageForKey:(NSString *)key;
//根据key中后缀@2x、@3x,放大图片到2、3倍 - (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image;
<pre><code>//在磁盘中查找图片
-
(NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock { if (!doneBlock) { return nil;
}
if (!key) { doneBlock(nil, SDImageCacheTypeNone); return nil;
}
// First check the in-memory cache... UIImage *image = [self imageFromMemoryCacheForKey:key]; if (image) { doneBlock(image, SDImageCacheTypeMemory); return nil;
}
<code><pre>//缓存中没有,则从硬盘查找,放在ioQueue中处理
NSOperation *operation = [NSOperation new]; dispatch_async(self.ioQueue, ^{ if (operation.isCancelled) { return; } @autoreleasepool { UIImage *diskImage = [self diskImageForKey:key]; if (diskImage && self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); [self.memCache setObject:diskImage forKey:key cost:cost]; } dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, SDImageCacheTypeDisk); }); } }); return operation;
}
//从缓存中移除图片,
-
(void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion { if (key == nil) { return; } if (self.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();
}
}
//从磁盘中移除缓存图片文件夹 -
(void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
//每次退到后台,清除已过期文件,并判断缓存文件夹是否大于设置的最大容量 -
(void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
//初始化缓存路径URL
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
//获取路径下文件的属性值,目录、文件修改日期、文件大小
NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];// 枚举器预先获取缓存文件的有用的属性 NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL includingPropertiesForKeys:resourceKeys options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL]; NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge]; NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary]; NSUInteger currentCacheSize = 0; NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
for (NSURL *fileURL in fileEnumerator) {
NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];// 是文件夹目录 if ([resourceValues[NSURLIsDirectoryKey] boolValue]) { continue; } // 文件最后一次修改的日期与过期日期比较谁更晚,laterDate函数返回两个日期中较晚的一个时间,如果修改日期小于过期日期则删除该文件 NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey]; if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) { [urlsToDelete addObject:fileURL]; continue; } // 统计未过期文件的大小 NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize += [totalAllocatedSize unsignedIntegerValue]; [cacheFiles setObject:resourceValues forKey:fileURL]; } // 删除已过期文件 for (NSURL *fileURL in urlsToDelete) { [_fileManager removeItemAtURL:fileURL error:nil]; } // 如果缓存文件容量已经大于设置的容量则删除最早的文件 if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) { // 最大缓存容量的一半 const NSUInteger desiredCacheSize = self.maxCacheSize / 2; // 由修改日期对文件排序 NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; }]; //删除文件直到文件夹的容量 < 最大容量的一半 for (NSURL *fileURL in sortedFiles) { if ([_fileManager removeItemAtURL:fileURL error:nil]) { NSDictionary *resourceValues = cacheFiles[fileURL]; NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize -= [totalAllocatedSize unsignedIntegerValue]; if (currentCacheSize < desiredCacheSize) { break; } }
}
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(); }); } });
}
// 获取已缓存文件的总容量
- (NSUInteger)getSize { __blockNSUInteger size = 0; dispatch_sync(self.ioQueue, ^{ NSDirectoryEnumerator *fileEnumerator = [_fileManagerenumeratorAtPath:self.diskCachePath]; for (NSString *fileName in fileEnumerator) { NSString *filePath = [self.diskCachePathstringByAppendingPathComponent:fileName]; NSDictionary *attrs = [[NSFileManagerdefaultManager] attributesOfItemAtPath:filePath error:nil]; size += [attrs fileSize]; } }); return size;}
// 获取已缓存图片的总数量
- (NSUInteger)getDiskCount { __blockNSUInteger count = 0; dispatch_sync(self.ioQueue, ^{ NSDirectoryEnumerator *fileEnumerator = [_fileManagerenumeratorAtPath:self.diskCachePath]; count = [[fileEnumerator allObjects] count]; }); return count;
}
// 计算文件大小及文件数量
- (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock { NSURL *diskCacheURL = [NSURLfileURLWithPath:self.diskCachePathisDirectory:YES]; dispatch_async(self.ioQueue, ^{ NSUInteger fileCount = 0; NSUInteger totalSize = 0; NSDirectoryEnumerator *fileEnumerator = [_fileManagerenumeratorAtURL:diskCacheURL includingPropertiesForKeys:@[NSFileSize] options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL]; for (NSURL *fileURL in fileEnumerator) { NSNumber *fileSize; [fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKeyerror:NULL]; totalSize += [fileSize unsignedIntegerValue]; fileCount += 1; } if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(fileCount, totalSize); }); } });
}