SDWebImage的缓存机制

SDWebImage缓存机制

存 取 删 路径

1.1 存

是在storeImage这个方法里:

将图片储存到内存和硬盘上

-(void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {
if (!image || !key) {
    return;
}
// if memory cache is enabled
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存在,但是需要重新计算(recalculate)或者data为空
        // 那就要根据image重新生成新的data
        // 不过要是连image也为空的话,那就别存了
        if (image && (recalculate || !data)) {
#if TARGET_OS_IPHONE
            // 我们需要判断image是PNG还是JPEG
            // PNG的图片很容易检测出来,因为它们有一个特定的标示 (http://www.w3.org/TR/PNG-Structure.html)
            // PNG图片的前8个字节不许符合下面这些值(十进制表示)
            // 137 80 78 71 13 10 26 10

            // 如果imageData为空l (举个例子,比如image在下载后需要transform,那么就imageData就会为空)
            // 并且image有一个alpha通道, 我们将该image看做PNG以避免透明度(alpha)的丢失(因为JPEG没有透明色)
            int alphaInfo = CGImageGetAlphaInfo(image.CGImage);// 获取image中的透明信息
            // 该image中确实有透明信息,就认为image为PNG
            BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
                              alphaInfo == kCGImageAlphaNoneSkipFirst ||
                              alphaInfo == kCGImageAlphaNoneSkipLast);
            BOOL imageIsPng = hasAlpha;

            // 但是如果我们已经有了imageData,我们就可以直接根据data中前几个字节判断是不是PNG
            if ([imageData length] >= [kPNGSignatureData length]) {
                // ImageDataHasPNGPreffix就是为了判断imageData前8个字节是不是符合PNG标志
                imageIsPng = ImageDataHasPNGPreffix(imageData);
            }

            // 如果image是PNG格式,就是用UIImagePNGRepresentation将其转化为NSData,否则按照JPEG格式转化,并且压缩质量为1,即无压缩
            if (imageIsPng) {
                data = UIImagePNGRepresentation(image);
            }
            else {
                data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
            }
#else
            // 当然,如果不是在iPhone平台上,就使用下面这个方法。不过不在我们研究范围之内
            data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif
        }

        // 获取到需要存储的data后,下面就要用fileManager进行存储了
        if (data) {
            // 首先判断disk cache的文件路径是否存在,不存在的话就创建一个
            // disk cache的文件路径是存储在_diskCachePath中的
            if (![_fileManager fileExistsAtPath:_diskCachePath]) {
                [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
            }

            // 根据image的key(一般情况下理解为image的url)组合成最终的文件路径
            // 上面那个生成的文件路径只是一个文件目录,就跟/cache/images/img1.png和cache/images/的区别一样
            NSString *cachePathForKey = [self defaultCachePathForKey:key];
            // 这个url可不是网络端的url,而是file在系统路径下的url
            // 比如/foo/bar/baz --------> file:///foo/bar/baz
            NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];

            // 根据存储的路径(cachePathForKey)和存储的数据(data)将其存放到iOS的文件系统
            [_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil];

            // disable iCloud backup
            if (self.shouldDisableiCloud) {
                [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
            }
        }
    });
}
}

1.2 取

内存缓存使用NSCache的objectForKey取数据:

- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
    return [self.memCache objectForKey:key];
}

磁盘取数据 不断用 dataWithContentsOfFile来试数据是否在key对应的路径中

- (UIImage *)imageFromDiskCacheForKey:(NSString *)key {

// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
    return image;
}

// Second check the disk cache...
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
    NSUInteger cost = SDCacheCostForImage(diskImage);
    [self.memCache setObject:diskImage forKey:key cost:cost];
}

return diskImage;
}

1.3 删

  1. removeImageForKeyfromDisk:withCompletion: // 异步地将image从缓存(内存缓存以及可选的磁盘缓存)中移除
  2. clearMemory // 清楚内存缓存上的所有image
  3. clearDisk // 清除磁盘缓存上的所有image
  4. cleanDisk // 清除磁盘缓存上过期的image

看其中最长的一个:

// 实现了一个简单的缓存清除策略:清除修改时间最早的file
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
    // 这两个变量主要是为了下面生成NSDirectoryEnumerator准备的
    // 一个是记录遍历的文件目录,一个是记录遍历需要预先获取文件的哪些属性
    NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
    NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

    // 递归地遍历diskCachePath这个文件夹中的所有目录,此处不是直接使用diskCachePath,而是使用其生成的NSURL
    // 此处使用includingPropertiesForKeys:resourceKeys,这样每个file的resourceKeys对应的属性也会在遍历时预先获取到
    // NSDirectoryEnumerationSkipsHiddenFiles表示不遍历隐藏文件
    NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                               includingPropertiesForKeys:resourceKeys
                                                                  options:NSDirectoryEnumerationSkipsHiddenFiles
                                                             errorHandler:NULL];
    // 获取文件的过期时间,SDWebImage中默认是一个星期
    // 不过这里虽然称*expirationDate为过期时间,但是实质上并不是这样。
    // 其实是这样的,比如在2015/12/12/00:00:00最后一次修改文件,对应的过期时间应该是
    // 2015/12/19/00:00:00,不过现在时间是2015/12/27/00:00:00,我先将当前时间减去1个星期,得到
    // 2015/12/20/00:00:00,这个时间才是我们函数中的expirationDate。
    // 用这个expirationDate和最后一次修改时间modificationDate比较看谁更晚就行。
    NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
    // 用来存储对应文件的一些属性,比如文件所需磁盘空间
    NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
    // 记录当前已经使用的磁盘缓存大小
    NSUInteger currentCacheSize = 0;

    // 在缓存的目录开始遍历文件.  此次遍历有两个目的:
    //
    //  1. 移除过期的文件
    //  2. 同时存储每个文件的属性(比如该file是否是文件夹、该file所需磁盘大小,修改时间)
    NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
    for (NSURL *fileURL in fileEnumerator) {
        NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];

        // 当前扫描的是目录,就跳过
        if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
            continue;
        }

        // 移除过期文件
        // 这里判断过期的方式:对比文件的最后一次修改日期和expirationDate谁更晚,如果expirationDate更晚,就认为该文件已经过期,具体解释见上面
        NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
        if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
            [urlsToDelete addObject:fileURL];
            continue;
        }

        // 计算当前已经使用的cache大小,
        // 并将对应file的属性存到cacheFiles中
        NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
        currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
        [cacheFiles setObject:resourceValues forKey:fileURL];
    }

    for (NSURL *fileURL in urlsToDelete) {
        // 根据需要移除文件的url来移除对应file
        [_fileManager removeItemAtURL:fileURL error:nil];
    }

    // 如果我们当前cache的大小已经超过了允许配置的缓存大小,那就删除已经缓存的文件。
    // 删除策略就是,首先删除修改时间更早的缓存文件
    if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
        // 直接将当前cache大小降到允许最大的cache大小的一般
        const NSUInteger desiredCacheSize = self.maxCacheSize / 2;

        // 根据文件修改时间来给所有缓存文件排序,按照修改时间越早越在前的规则排序
        NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                        usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                            return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                        }];

        // 每次删除file后,就计算此时的cache的大小
        // 如果此时的cache大小已经降到期望的大小了,就停止删除文件了
        for (NSURL *fileURL in sortedFiles) {
            if ([_fileManager removeItemAtURL:fileURL error:nil]) {
                // 获取该文件对应的属性
                NSDictionary *resourceValues = cacheFiles[fileURL];
    // 根据resourceValues获取该文件所需磁盘空间大小
                NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
    // 计算当前cache大小
                currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];

                if (currentCacheSize < desiredCacheSize) {
                    break;
                }
            }
        }
    }
    // 如果有completionBlock,就在主线程中调用
    if (completionBlock) {
        dispatch_async(dispatch_get_main_queue(), ^{
            completionBlock();
        });
    }
});
}

1.4 图片储存路径

// 简单封装了cachePathForKey:inPath
- (NSString *)defaultCachePathForKey:(NSString *)key {
    return [self cachePathForKey:key inPath:self.diskCachePath];
}

// cachePathForKey:inPath
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path {
// 根据传入的key创建最终要存储时的文件名
NSString *filename = [self cachedFileNameForKey:key];
// 将存储的文件路径和文件名绑定在一起,作为最终的存储路径
return [path stringByAppendingPathComponent:filename];
}

// cachedFileNameForKey:
- (NSString *)cachedFileNameForKey:(NSString *)key {
const char *str = [key UTF8String];
if (str == NULL) {
    str = "";
}
// 使用了MD5进行加密处理
// 开辟一个16字节(128位:md5加密出来就是128bit)的空间
unsigned char r[CC_MD5_DIGEST_LENGTH];
// 官方封装好的加密方法
// 把str字符串转换成了32位的16进制数列(这个过程不可逆转) 存储到了r这个空间中
CC_MD5(str, (CC_LONG)strlen(str), r);
// 最终生成的文件名就是 "md5码"+".文件类型"
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                      r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                      r[11], r[12], r[13], r[14], r[15], [[key pathExtension] isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", [key pathExtension]]];

return filename;
}
时间: 2024-11-06 00:02:45

SDWebImage的缓存机制的相关文章

SDWebImage 图片缓存机制

SDWebImage与iOS系统自带的缓存相比,优势是什么? 从iOS5开始,iOS NSURLCache对于原生得HTTP Response做内存和磁盘缓存.每一次缓存命中(缓存命中就是说内存或者磁盘中有要请求的数据)的时候,app必须把原生的数据转成UIImage.这需要很多操作,比如数据分析转化.内存拷贝等等. 另一方面,SDWebImage在内存中缓存UIImage,在磁盘上存原来的压缩的(但是,已经解码)image文件.UIImage用NSCache存在内存里,所以不需要拷贝操作,并且

浏览器缓存机制浅析

非HTTP协议定义的缓存机制 浏览器缓存机制,其实主要就是HTTP协议定义的缓存机制(如: Expires: Cache-control等).但是也有非HTTP协议定义的缓存机制,如使用HTML Meta 标签,Web开发者可以在HTML页面的<head>节点中加入<meta>标签,代码如下: <META HTTP-EQUIV="Pragma" CONTENT="no-cache"> 上述代码的作用是告诉浏览器当前页面不被缓存,每

Hibernate 缓存机制

一.why(为什么要用Hibernate缓存?) Hibernate是一个持久层框架,经常访问物理数据库. 为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能. 缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据. 二.what(Hibernate缓存原理是怎样的?)Hibernate缓存包括两大类:Hibernate一级缓存和Hibernate二级缓存. 1.Hibernate一级缓存又称为“Session的

Android开源框架ImageLoader:加载图片的三级缓存机制

前言:可从  https://github.com/nostra13/Android-Universal-Image-Loader 下载三级缓存机制的开源框架.下文简单介绍该框架中主要的常用方法,掌握这些方法,基本就可应对多数图片下载的需求. 注意:以下代码为示意代码片断,仔细读一下应能知道怎么用.蓝色表示为开源框架中的类. 1.初始化ImageLoader类对象: ImageLoader imageLoader = ImageLoader.getInstance(); imageLoader.

Varnish缓存机制详细介绍及简单配置

Varnish是一款高性能的开源HTTP加速器,其主要用来做为反向代理中的缓存服务器使用,但其实Varnish本身也是具有反向代理功能的,但在创建连接和维持连接上,与Nginx相比差距很大,现在有一个很流行的架构就是前端用Nginx作为反向代理,后面加Varnish缓存服务器为Web服务加速 在将Varnish前先谈谈我们的浏览器缓存机制,现在的浏览器基本都具有缓存功能,它能将我们以前访问过的静态内容和可进行缓存的动态内容缓存再本地,而后在下次访问相同资源时,如果可以确认Server端的资源未发

【腾讯Bugly干货分享】彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法

本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/qOMO0LIdA47j3RjhbCWUEQ 作者:李志刚 导语 Http 缓存机制作为 web 性能优化的重要手段,对从事 Web 开发的小伙伴们来说是必须要掌握的知识,但最近我遇到了几个缓存头设置相关的题目,发现有好几道题答错了,有的甚至在知道了正确答案后依然不明白其原因,可谓相当的郁闷呢!!为了确认下是否只是自己理解不深,我特意请教了其他几位小伙

hibernate缓存机制详细分析 复制代码

您可以通过点击 右下角 的按钮 来对文章内容作出评价, 也可以通过左下方的 关注按钮 来关注我的博客的最新动态. 如果文章内容对您有帮助, 不要忘记点击右下角的 推荐按钮 来支持一下哦 如果您对文章内容有任何疑问, 可以通过评论或发邮件的方式联系我: [email protected] / [email protected] 如果需要转载,请注明出处,谢谢!! 在本篇随笔里将会分析一下hibernate的缓存机制,包括一级缓存(session级别).二级缓存(sessionFactory级别)以

Java缓存学习之三:CDN缓存机制

CDN是什么? 关于CDN是什么,此前网友详细介绍过. CDN是Content Delivery Network的简称,即"内容分发网络"的意思.一般我们所说的CDN加速,一般是指网站加速或者用户下载资源加速. 举个通俗的例子: 谈到CDN的作用,可以用8年买火车票的经历来形象比喻:8年前,还没有火车票代售点一说,12306.cn更是无从说起.那时候火车票还只能在火车站的售票大厅购买,而我所住的小县城并不通火车,火车票都要去市里的火车站购买,而从县城到市里,来回就是4个小时车程,简直就

10.hibernate缓存机制详细分析(转自xiaoluo501395377)

hibernate缓存机制详细分析 在本篇随笔里将会分析一下hibernate的缓存机制,包括一级缓存(session级别).二级缓存(sessionFactory级别)以及查询缓存,当然还要讨论下我们的N+1的问题. 随笔虽长,但我相信看完的朋友绝对能对hibernate的 N+1问题以及缓存有更深的了解. 一.N+1问题 首先我们来探讨一下N+1的问题,我们先通过一个例子来看一下,什么是N+1问题: list()获得对象: 1 /** 2 * 此时会发出一条sql,将30个学生全部查询出来