SDWebImage 源码解读之缓存类SDImageCache

本章开始将介绍SDWebImage库中图片缓存策略类SDImageCache,首先解释一下涉及到的一些基本概念和方法:

•NSCache

我们在对一个APP数据做存储和内存优化的时候,不可避免的需要对缓存做相应的处理,而且缓存处理的优劣,往往也是决定一个APP能否长线发展的重要因素之一。NSCache是苹果官方提供的一套缓存机制,主要作用于内存缓存的管理方面,NSCache在系统内存很低时,会自动释放一些对象(出自苹果官方文档,不过在模拟器中模拟内存警告时,不会做缓存的清理动作) 为了确保接收到内存警告时能够真正释放内存,最好调用一下removeAllObjects方法。在没有引入NSCache之前,我们要管理缓存,都是使用的NSMutableDictionary来管理,然而,使用NSMutableDictionary来管理缓存是有些不妥的, 知道多线程操作原理的开发者都明白, NSMutableDictionary在线程方面来说是不安全,这也是苹果官方文档明确说明了的,而如果使用的是NSCache,那就不会出现这些问题。

NSCache和NSMutableDictionary的相同点与区别:

相同点:NSCache和NSMutableDictionary功能用法基本是相同的。

区别:

1.NSCache是线程安全的,NSMutableDictionary线程不安全

2.当内存不足时NSCache会自动释放内存(所以从缓存中取数据的时候总要判断是否为空)

3.NSCache可以指定缓存的限额,当缓存超出限额自动释放内存。

NSCache属性和方法介绍:

1)属性介绍

name:名称

delegete:设置代理

totalCostLimit:缓存空间的最大总成本,超出上限会自动回收对象。默认值为0,表示没有限制

countLimit:能够缓存的对象的最大数量。默认值为0,表示没有限制

evictsObjectsWithDiscardedContent:标识缓存是否回收废弃的内容

2)方法介绍

- (void)setObject:(ObjectType)obj forKey:(KeyType)key;//在缓存中设置指定键名对应的值,0成本

- (void)setObject:(ObjectType)obj forKey:(KeyType)keycost:(NSUInteger)g;

//在缓存中设置指定键名对应的值,并且指定该键值对的成本,用于计算记录在缓存中的所有对象的总成本

//当出现内存警告或者超出缓存总成本上限的时候,缓存会开启一个回收过程,删除部分元素

- (void)removeObjectForKey:(KeyType)key;//删除缓存中指定键名的对象

- (void)removeAllObjects;//删除缓存中所有的对象

代码示例:

SDWebImage 源码解读之缓存类SDImageCache_第1张图片
SDWebImage 源码解读之缓存类SDImageCache_第2张图片

•MD5加密

MD5加密是最常用的加密方法之一,是从一段字符串中通过相应特征生成一段32位的数字字母混合码。对输入信息生成唯一的128位散列值(32个字符)。软件开发过程中,对数据进行加密是保证数据安全的重要手段,常见的加密有Base64加密和MD5加密。Base64加密是可逆的,MD5加密目前来说一般是不可逆的。MD5生成的是固定的128bit,即128个0和1的二进制位,而在实际应用开发中,通常是以16进制输出的,所以正好就是32位的16进制,说白了也就是32个16进制的数字。MD5主要特点是 不可逆,相同数据的MD5值肯定一样,不同数据的MD5值不一样。MD5算法具有以下性质:

1、压缩性:任意长度的数据,算出的MD5值长度都是固定的。

2、容易计算:从原数据计算出MD5值很容易。

3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。

4、弱抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。

5、强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。

6、MD5加密是不可解密的,但是网上有一些解析MD5的,那个相当于一个大型的数据库,通过匹配MD5去找到原密码。只要在要加密的字符串前面加上一些字母数字符号或者多次MD5加密,这样出来的结果一般是解析不出来的。下面举例讲一下MD5的使用:

#import@interface MD5Encrypt : NSObject

// MD5加密

/*由于MD5加密是不可逆的,多用来进行验证*/

// 32位小写

+(NSString *)MD5ForLower32Bate:(NSString *)str;

// 32位大写

+(NSString *)MD5ForUpper32Bate:(NSString *)str;

// 16为大写

+(NSString *)MD5ForUpper16Bate:(NSString *)str;

// 16位小写

+(NSString *)MD5ForLower16Bate:(NSString *)str;

@end

#import "MD5Encrypt.h"

#import

@implementation MD5Encrypt

#pragma mark - 32位 小写

+(NSString *)MD5ForLower32Bate:(NSString *)str{

//要进行UTF8的转码

const char* input = [str UTF8String];

unsigned char result[CC_MD5_DIGEST_LENGTH];

CC_MD5(input, (CC_LONG)strlen(input), result);

NSMutableString *digest = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];

for (NSInteger i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {

[digest appendFormat:@"%02x", result[i]];

}

return digest;

}

#pragma mark - 32位 大写

+(NSString *)MD5ForUpper32Bate:(NSString *)str{

//要进行UTF8的转码

const char* input = [str UTF8String];

unsigned char result[CC_MD5_DIGEST_LENGTH];

CC_MD5(input, (CC_LONG)strlen(input), result);

NSMutableString *digest = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];

for (NSInteger i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {

[digest appendFormat:@"%02X", result[i]];

}

return digest;

}

#pragma mark - 16位 大写

+(NSString *)MD5ForUpper16Bate:(NSString *)str{

NSString *md5Str = [self MD5ForUpper32Bate:str];

NSString *string;

for (int i=0; i<24; i++) {

string=[md5Str substringWithRange:NSMakeRange(8, 16)];

}

return string;

}

#pragma mark - 16位 小写

+(NSString *)MD5ForLower16Bate:(NSString *)str{

NSString *md5Str = [self MD5ForLower32Bate:str];

NSString *string;

for (int i=0; i<24; i++) {

string=[md5Str substringWithRange:NSMakeRange(8, 16)];

}

return string;

}

@end

关于解读SDWebImage库中图片缓存策略类SDImageCache.h的实现思路,我们可以借助下面这张路线图片去理解:

SDWebImage 源码解读之缓存类SDImageCache_第3张图片

一.SDImageCache缓存策略之初始化


二.SDImageCache缓存策略之查询

SDImageCache查询操作的具体实现是在下面的方法中进行的:

SDWebImage 源码解读之缓存类SDImageCache_第4张图片

该方法有两个参数值,一个是key,一个是doneBlock,前者是作为图片资源的唯一标识符,不管是内存缓存机制中的NSCache还是本地存储的文件数据,都是以该key值为准去获取数据。doneBlock参数则是向上一级传递当前查询结果的操作。在查询操作的一开始阶段,获取图片的数据会使用- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key方法先从内存数据中进行查看,如果内存中已经存在了该图片数据则直接返回给上一级,如果并未发现资源,则会创建一个NSOperation对象,去异步的执行从本地保存的数据中查询是否有该数据,如有则将数据引入到内存中,并在主线程执行doneBlock回调操作,将结果告知上一级,同时释放资源。单纯只是查询本地文件是否存在的话则使用的是下面的方法:

SDWebImage 源码解读之缓存类SDImageCache_第5张图片

三.SDImageCache缓存策略之删除

SDImageCache删除操作的具体实现是在下面的方法中进行的:

SDWebImage 源码解读之缓存类SDImageCache_第6张图片

前面三个方法都是基于第四个方法进行实现的,现在主要来看看第四个方法的实现,该方法中有三个参数中,一个是key,一个是fromDisk,一个是completion,key值的作用之前已经介绍过了,这里就不在进行说明,fromDisk参数是一个bool值,该值的作用是去判断在删除内存中的数据时需不需要同时删除本地的缓存数据,completion则是完成操作的回调,向上一级传递当前删除操作的结果。在该方法中,删除操作行为都会删除内存中的图片数据(除非有特定设置),之后根据需要异步去删除本地缓存中的图片数据,并在主线程中告之上一级结果。

四.SDImageCache缓存策略之保存

SDImageCache保存操作的具体实现是在下面的方法中进行的:

SDWebImage 源码解读之缓存类SDImageCache_第7张图片
SDWebImage 源码解读之缓存类SDImageCache_第8张图片

中间两个方法都是基于第一个方法进行实现的,现在主要来看看第一个方法的实现,该方法中有五个参数中,一个是image,一个是recalculate,一个是imageData,一个是key,最后一个是toDisk。image和imageData,都是图片的数据,只不过一个以UIImage类型,一个是NSData类型,recalculate该值的作用是去判断图片的二进制数据有没有进行图片加密处理,toDisk则是判断当前图片数据是否要保存到本地。在进行保存操作是,会先将图片数据保存到内存中,以下代码就是为了实现该目的:

if (self.shouldCacheImagesInMemory) {

NSUInteger cost = SDCacheCostForImage(image);

[self.memCache setObject:image forKey:key cost:cost];

}

图片数据被保存到内存中之后,接下来会异步进行保存到本地的操作,在保存到本地的操作是,先对图片的类型进行判断,需要注意的是SDWebImage库中也提供了,对图片类型的判断方法,但是这些方法针对的是NSData类型的判断,并不是UIImage类型的判断,这里需要区分一下,而对于UIImage类型的判断作者提供了一下的方式去判断:

int alphaInfo = CGImageGetAlphaInfo(image.CGImage);

BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||

alphaInfo == kCGImageAlphaNoneSkipFirst ||

alphaInfo == kCGImageAlphaNoneSkipLast);

BOOL imageIsPng = hasAlpha;

// But if we have an image data, we will look at the preffix

if ([imageData length] >= [kPNGSignatureData length]) {

imageIsPng = ImageDataHasPNGPreffix(imageData);

}

if (imageIsPng) {

data = UIImagePNGRepresentation(image);

}

else {

data = UIImageJPEGRepresentation(image, (CGFloat)1.0);

}

判断的依据有两个:

1.PNG有一个独特的签名,前8个字节总是包含137 80 78 71 13 10 26 10

2.PNG图片存在一个透明度属性值,JPG图片没有透明的背景,而PNG图像可以保留透明的背景

满足这两个条件中的其中一个,就可以判定该图片为PNG,之后根据对象的方法UIImagePNGRepresentation(image)/UIImageJPEGRepresentation(image, (CGFloat)1.0)将图片对象UIImage转化成二进制数据并调用上面图片的第四个方法将数据保存到本地指定的路径中。

五.SDImageCache缓存策略之读取

获取本地的图片资源会使用到下面的两个方法:

SDWebImage 源码解读之缓存类SDImageCache_第9张图片

第一个方法中先使用方法- (NSString *)defaultCachePathForKey:(NSString *)key获取key值在默认情况下保存的路径,该路径是绝对的路径匹配,不仅匹配到文件名,还匹配到图片的后缀类型,之后利用NSData的+ (instancetype)dataWithContentsOfFile:(NSString *)path方法,获取本地的图片二进制数据,如果绝对路径无法匹配到,则会使用相对匹配的方法,相对匹配无法找到对应数据,则会再去遍历用户自定义路径,直到找到图片数据为止。

第二个方法的作用则是将获取到的图片二进制数据进行转化为UIImage对象,并对图片对象进行还原跟解码操作,保证图片与网络图片的一致性。

六.SDImageCache缓存策略之清除

关于清除操作,SDImageCache提供了三种情况下的清除操作,以保证App能够在特定情况下及时对缓存进行清除,避免出现问题。

a.自动清除机制

SDImageCache在初始化之初就创建了一个名为AutoPurgeCache的对象,该对象会去监听App抛出内存警告的通知UIApplicationDidReceiveMemoryWarningNotification,一旦App收到该内存警告通知,将首先进行清除内存中的缓存数据,代码如下:

SDWebImage 源码解读之缓存类SDImageCache_第10张图片

b.内存警告清除以及手动清除本地缓存数据

SDImageCache在创建时会添加一个观察者用以监听UIApplicationDidReceiveMemoryWarningNotification通知,一旦App收到该内存警告通知,将会调用以下方法进行清除缓存数据,使用者也可以根据场景需要手动清除本地的缓存数据,清除操作将会把本地的整个缓存目录进行清除,代码如下:

SDWebImage 源码解读之缓存类SDImageCache_第11张图片

c.后台状态已经程序即将关闭状态清除缓存

当程序处于后台时,SDWebImage库会开启后台任务机制,向系统申请更多的时间去处理线程任务,以保证即使App处于后台状态也能有足够的时间去清除缓存数据,代码如下:

SDWebImage 源码解读之缓存类SDImageCache_第12张图片
SDWebImage 源码解读之缓存类SDImageCache_第13张图片
SDWebImage 源码解读之缓存类SDImageCache_第14张图片

现在主要看看第二个方法的实现,该方法通过异步线程的方式在默认路径下对所有文件进行进行过滤操作,这里面有三个枚举值:

NSURLIsDirectoryKey:允许你判断遍历到的URL所指对象是否是目录.

NSURLContentModificationDateKey:允许你判断遍历返回的URL所指项目的最后

修改时间.

NSURLTotalFileAllocatedSizeKey:允许你判断遍历返回的URL所指项目的文件的总大小

NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL

includingPropertiesForKeys:resourceKeys

options:NSDirectoryEnumerationSkipsHiddenFiles

errorHandler:NULL];

上面代码执行之后获取到的值是满足上面三个枚举值的目录下所有文件的URL路径,接下来代码中对fileEnumerator中的URL进一步进行过滤,剔除目录路径的URL,之后将最后修改时间超过一个星期的缓存URL(SDWebImage本地缓存的默认的时间为一个星期)放入一个待删除的数组urlsToDelete中,同时保存每个文件的文件大小。如果本地缓存的图片数据的总大小超过设置的最大值的话,则会根据文件的最后修改时间进行排序,删除修改时间靠后的文件,以保证本地的缓存数据不超过最大值。

你可能感兴趣的:(SDWebImage 源码解读之缓存类SDImageCache)