其实在SDWebImage中有一个方法就可以把图片缓存到内存中和磁盘中,还是一样的套路,一个长方法,关于这个函数其实不难
代码注解写在里面,可供观看。
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
//判断图片或者是key存不存在,如果有一个不存在就直接进行回调,然后返回
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled 意思就是如果说内存缓存可用利用config的shouldCacheImagesInMemory进行判断
if (self.config.shouldCacheImagesInMemory) {
//利用一个C语言的函数去计算图片的大小,这里是以像素为单位,为什么这么说,详细看下面
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}
if (toDisk) {
/*异步io串行队列,为什么用串行?因为涉及到写入操作,怕对应的key话发生混乱,也就是怕产生的文件名和存入磁盘的数据不对应
所以磁盘存储都要一个个存储,这样就可以不用去加锁了,为什么要进行加锁,
因为并发情况下这些存储操作都不是线程安全的,但使用了串行队列就完全不需要考虑加锁释放锁
*/
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
//如果data不存在,但是image存在就走下面的if条件里面
if (!data && image) {
// If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
SDImageFormat format;
/**检查是否包含alpha通道,因为jpeg是有损压缩格式, 将像素信息用jpeg保存成文件再读取出来,其中某些像素值会有少许变化。没有透明信息
jpeg比较适合用来存储相机拍摄出来的图片
png是一种无损压缩格式,它可以有透明效果,png比较适合矢量图,几何图.
所以下面如果包含Alpha的图片就把它的格式定义为PNG,如果没有则定位为JPEG
*/
if (SDCGImageRefContainsAlpha(image.CGImage)) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
//然后再根据format进行对UIImage编码,编码过程,这里指的就是将一个UIImage表示的图像,编码为对应图像格式的数据,输出一个NSData的过程
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
}
//存储到磁盘中
[self _storeImageDataToDisk:data forKey:key];
}
//判断回调是否存在
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
}
这里先介绍下计算图像消耗内存的方法,在MAC端就是返回图片的宽度乘图像的高度,就是图片的像素个数,而在ios和Watch端那么就和一个scale有关了,如果一张图片名字包含:....@2x 则这张图片的scale属性默认就被设置为了2.其他的图片都是默认的scale,为1,image的size属性,乘以scale属性,就是这个图片实际的像素值. 其中FOUNDATION_STATIC_INLINE表示该函数是一个具有文件内部访问权限的静态内联函数,内联函数就是建议编译器在调用时将函数展开。
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
}
这里再介绍下上面调用到的几个方法
BOOL SDCGImageRefContainsAlpha(CGImageRef imageRef) {
//如果imageRef为空就直接返回NO
if (!imageRef) {
return NO;
}
//获取图片的Alpha的信息
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef);
//满足括号中的任何一个,都说明是没有Alpha通道的
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
return hasAlpha;
}
接下来再介绍下编码的方法刚开始是调用的这个方法,然后先去遍历一个个编码器判断能不能进行编码
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
if (!image) {
return nil;
}
for (id coder in self.coders) {
if ([coder canEncodeToFormat:format]) {
return [coder encodedDataWithImage:image format:format];
}
}
return nil;
}
判断的方法调用的是SDWebImageImageIOCoder中的canEncodeToFormat方法,WebP不支持,根据本机类型来判断是否支持HEIC,HEIC是为苹果而设定的图像格式,自IOS11出现。
#pragma mark - Encode
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
switch (format) {
case SDImageFormatWebP:
// Do not support WebP encoding
return NO;
case SDImageFormatHEIC:
// Check HEIC encoding compatibility
return [[self class] canEncodeToHEICFormat];
default:
return YES;
}
}
判断是否支持HEIC图像格式是通过能否根据相应的格式生成CGImageDestinationRef来判断的
+ (BOOL)canEncodeToHEICFormat {
static BOOL canEncode = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatHEIC];
// Create an image destination.
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
if (!imageDestination) {
// Can't encode to HEIC
canEncode = NO;
} else {
// Can encode to HEIC
CFRelease(imageDestination);
canEncode = YES;
}
});
return canEncode;
}
之后我们回到循环编码器数组判断能否编码的那边,那里的self.coders其实是来源于SDWebImageCodersManager中的mutableCoders,其中mutableCoders里面包含的编码器在manager的init方法中会被加入,SDWebImageCodersManager是一个编码解码管理器,处理多个图片编码解码任务,编码器数组是一个优先级队列,这意味着后面添加的编码器将具有最高优先级。当图片从网络中获取到的时候就进行解压缩。当图片从磁盘缓存中获取到的时候立即解压缩。
所以我们判断能否解码和编码用的都是self.coders,那么这个是怎么赋值的呢?从mutableCoders拷贝过来,然后反转一下,这样一来最后面的编码器就在了最前面
如果可以编码会调用下面的这个方法
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
//图片不存在就返回image
if (!image) {
return nil;
}
//再去判断下格式是否是不明确的
if (format == SDImageFormatUndefined) {
//这边是根据是否含有Alpha通道来判断
BOOL hasAlpha = SDCGImageRefContainsAlpha(image.CGImage);
if (hasAlpha) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
}
//创建要写入的数据对象
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:format];
// Create an image destination.
/**CGImageDestinationRef将图片数据写入目的地,并且负责做图片编码,CGImageDestination用于将照片写入到用户的磁盘中,在写入前可修改元数据
参数1:要写入的数据对象
参数2:生成的图像文件的统一类型标识符
参数3:图像文件将包含的图像数量,不包含缩略图
参数4:留给未来使用,保留为NULL。
*/
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
if (!imageDestination) {
// Handle failure.
return nil;
}
//创建属性
NSMutableDictionary *properties = [NSMutableDictionary dictionary];
#if SD_UIKIT || SD_WATCH
//转换ios图像的方向到EXIF格式的方向
/**
EXIF信息官方的话语
EXIF信息,是可交换图像文件的缩写,是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。
EXIF可以附加于JPEG、TIFF、RIFF等文件之中,为其增加有关数码相机拍摄信息的内容和索引图或图像处理软件的版本信息
*/
NSInteger exifOrientation = [SDWebImageCoderHelper exifOrientationFromImageOrientation:image.imageOrientation];
//设置相应的属性
[properties setValue:@(exifOrientation) forKey:(__bridge_transfer NSString *)kCGImagePropertyOrientation];
#endif
// Add your image to the destination.
//添加图片和元信息到imageDestination中
CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
// Finalize the destination.
//最后调用,完成数据写入,如果图像成功写入就返回YES,否则返回NO
if (CGImageDestinationFinalize(imageDestination) == NO) {
// Handle failure.
imageData = nil;
}
//结束要释放这个对象
CFRelease(imageDestination);
return [imageData copy];
}
根据format转换成相应的CFStringRef类型的格式
+ (nonnull CFStringRef)sd_UTTypeFromSDImageFormat:(SDImageFormat)format {
CFStringRef UTType;
switch (format) {
case SDImageFormatJPEG:
UTType = kUTTypeJPEG;
break;
case SDImageFormatPNG:
UTType = kUTTypePNG;
break;
case SDImageFormatGIF:
UTType = kUTTypeGIF;
break;
case SDImageFormatTIFF:
UTType = kUTTypeTIFF;
break;
case SDImageFormatWebP:
UTType = kSDUTTypeWebP;
break;
case SDImageFormatHEIC:
UTType = kSDUTTypeHEIC;
break;
default:
// default is kUTTypePNG
UTType = kUTTypePNG;
break;
}
return UTType;
}
经过上述的一系列的判断之后,就可以加入到磁盘缓存中了,调用了下面这个方法
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key
调用位置如下所示
点进去看
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
//判断imageData存不存在还有key如果有一个不存在,就返回
if (!imageData || !key) {
return;
}
//判断是否存在_diskCachePath,这个就是磁盘缓存路径
if (![self.fileManager fileExistsAtPath:_diskCachePath]) {
[self.fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// get cache Path for image key
//获取图片的缓存路径,这里就是图片的url的md5的结果,然后拼接磁盘路径
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// transform to NSUrl
//转换为NSURL
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
//写入到文件中
[imageData writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
// disable iCloud backup 这里做的就是判断是否需要iCloud备份
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
还有一个需要提一下的就是下面这个方法
/*
参数1:指定要创建的目录的文件URL。如果要指定相对路径,则必须在创建相应的NSURL对象之前设置当前工作目录。这个参数不能为nil。
参数2:如果YES,该方法将创建任何不存在的父目录,作为在path中创建目录的一部分。如果是NO,则该方法在任何中间父目录不存在时失败。
如果任何中间路径元素对应于文件而不是目录,则此方法也会失败。
参数3:如果为nil,那么该方法为路径中任何新创建的目录的所有者、组和权限使用默认的值集,
权限是根据当前进程的umask设置的。有关更多信息,请参见umask。
所有者ID被设置为进程的有效用户ID。
组ID被设置为父目录。
所创建目录的属性则采用系统默认设置,一般会将目录的用户设置为root,访问权限设置为0755,这样就导致其他用户向这个目录写入时失败。关于这个
可以参考:
关于attributes的key在NSFileManager.h头文件中
参数4:指向错误对象的指针。如果出现错误,则将此指针设置为包含错误信息的实际错误对象。如果不想要错误信息,可以为这个参数指定nil。*/
参考此网站
- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(nullable NSDictionary
上面将NSURLIsExcludedFromBackupKey设置为YES的作用就是禁止iCloud备份,false的话就是允许备份的,这个设置不适用与用户的documents目录。
true if resource should be excluded from backups, false otherwise (Read-write, value type boolean NSNumber). This property is only useful for excluding cache and other application support files which are not needed in a backup. Some operations commonly made to user documents will cause this property to be reset to false and so this property should not be used on user documents.