SDWebImage源码解析(二)——SDImageCache缓存模块

第二篇的写在前面

本系列的上一篇文章已经从整个SDWebImage的框架和流程图入手介绍了WebCache+WebManager模块。在发布了这个系列的第一篇文章之后,我也去参考了一下相关的同样介绍SDWebImage框架的的文章,就是希望自己的解析能够更准确一些。同样有的文章把源码中所有英文注释都去掉替换成了自己翻译+解释的中文注释,但是我觉得如果有一定英文阅读能力,直接给出源码中对相关语义的解释可能更为直接一些,也防止了博客作者一定程度上的曲解。
在本系列末尾会列出参考文章。
好了,废话不多说,直接进入正题。

从上一篇继续

上一篇的大部分篇幅,使用了源码+注释的方式介绍了SDWebImageManager模块下的loadImageWithURL()方法,该方法用于通过调用者传入的URL从网络/缓存获取图片。其中有一个重要的方法:

//请求缓存
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
           //do something.�
}];

这个方法是属于本篇文章主角——SDImageCache类内部的方法。

SDImageCache + SDImageCacheConfig

SDWebImage的缓存模块有两个类:SDImageCache和一个辅助类SDImageCacheConfigSDImageCacheConfig 用于设置与缓存相关的一些属性,与上文一样,在文章中如果有涉及到会单独将这个属性拿出来作解释。

SDWebImageManager类似,SDImageCache同样被设计为一个单例类,内部提供了一个全能初始化(designated initializer)方法:

/**
 * Init a new cache store with a specific namespace and directory
 *
 * @param ns        The namespace to use for this cache store
 * @param directory Directory to cache disk images in
 */

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory {
    if ((self = [super init])) {
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
        
        // 初始化了一个串行的队列赋值给自身成员变量ioQueue
        //后面介绍的代码会使用到这个串行队列
        _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
        //初始化成员变量config
        _config = [[SDImageCacheConfig alloc] init];
        
        // Init the memory cache 初始化内存缓存 使用NSCache类实现
        /*AutoPurgeCache 是 继承于NSCache的一个类 里面封装了对系统    
        UIApplicationDidReceiveMemoryWarningNotification
         通知的监听,当收到该通知时,移除内部所有对象
        */
        _memCache = [[AutoPurgeCache alloc] init];
        _memCache.name = fullNamespace;

        //初始化磁盘缓存
        //directory是传入的磁盘缓存将要存放的路径
        if (directory != nil) {// 传入的directory参数不为nil
            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
        } else {// 传入的directory == nil
            NSString *path = [self makeDiskCachePath:ns];
            /* 
             makeDiskCachePath: 创建目标文件路径
             NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
             return [paths[0] stringByAppendingPathComponent:fullNamespace];
             */
            _diskCachePath = path;
        }
        //在ioQueue中初始化成员变量fileManager
        dispatch_sync(_ioQueue, ^{
            _fileManager = [NSFileManager new];
        });

#if SD_UIKIT
        // Subscribe to app events
        //监听系统通知
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(deleteOldFiles)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundDeleteOldFiles)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
#endif
    }

    return self;
}

请求缓存

如果说我们要自己设计一个图片缓存模块,那么最基本最核心的功能自然是:

  1. 从缓存中获取图片。
  2. 将图片缓存。
  3. 缓存管理机制

同样的,SDWebImage在设计图片缓存模块的时候也遵循着这个思路。

请求缓存queryCacheOperation方法

上文提及的在loadImageWithURL()方法中,manager通过调用queryCacheOperation方法请求缓存。下面给出这部分代码:

/*
 typedef void(^SDCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType);
 */
/*通过传入的key从缓存中查找图片,通过回调block的方式返回给调用者*/
/*完成回调SDCacheQueryCompletedBlock的定义在上面给出*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    if (!key) {
        //如果key不存在,执行回调,返回
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    //首先根据key检查in-memory的缓存中有没有图片
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {//在内存中获取到了图片
        NSData *diskData = nil;
        if ([image isGIF]) {
             //在所有keyPaths中根据key使用[NSData dataWithContentsOfFile]方法获取data
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        if (doneBlock) {
            //执行回调将image 和 data传出, 然后返回
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }
    /*内存中未查找到缓存图片,继续从磁盘缓存中查找*/
    //创建一个新的operation 由于下面的缓存查找操作会在子线程中异步执行
    //所以这里直接返回该operation给manager
    NSOperation *operation = [NSOperation new];
    
    dispatch_async(self.ioQueue, ^{
        //在ioQueue 执行缓存查找操作 不阻塞主线程
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }
        //生成一个新的autoreleasepool
        //获取磁盘缓存
        @autoreleasepool {
            //在所有keyPaths中根据key使用[NSData dataWithContentsOfFile]方法获取data
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            //根据key获取image
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                //在磁盘缓存中获取到image
                //访问config的shouldCacheImagesInMemory属性判断是否需要将图片缓存到内存中
                //计算空间花销 返回值为该图片的像素点个数
                NSUInteger cost = SDCacheCostForImage(diskImage);
               //将图片缓存到内存中
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    //主线程执行回调
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });

    return operation;
}

在这个方法中,有几个方法的实现在下面会具体介绍:

diskImageForKey方法

这个方法通过访问磁盘缓存获取图片。

//在磁盘缓存中根据key查找图片
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
    //1.获取data
    NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
    if (data) {
        //2. 根据data 获取 image
        UIImage *image = [UIImage sd_imageWithData:data];
        
        //3. 返回大小比例缩放正确的图片
        image = [self scaledImageForKey:key image:image];
        
        if (self.config.shouldDecompressImages) {
            //如果图片需要解压 使用SDWebImageDecoder进行解码
            /*
             Decompressing images that are downloaded and cached can improve performance but can consume lot of memory.
             默认为YES
             */
            image = [UIImage decodedImageWithImage:image];
        }
        return image;
    }
    else {
        return nil;
    }
}

在第三步中,image获取到一个缩放过后的图片。通常关心这个函数到底以一个怎样的系数或者机制来缩放。[self scaledImageForKey:key image:image];调用了下面的C函数。

//C 内联函数
inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) {
    if (!image) {
        return nil;
    }
   if ((image.images).count > 0) {
        //对于animated image进行处理
        //每一帧的图片都要进行scale操作
        NSMutableArray<UIImage *> *scaledImages = [NSMutableArray array];

        for (UIImage *tempImage in image.images) {
            
            [scaledImages addObject:SDScaledImageForKey(key, tempImage)];
        }
        return [UIImage animatedImageWithImages:scaledImages duration:image.duration];
    }
    else {
        if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
            CGFloat scale = 1;
            //获取缩放比例
            if (key.length >= 8) {
                NSRange range = [key rangeOfString:@"@2x."];
                if (range.location != NSNotFound) {
                    scale = 2.0;
                }
            
                range = [key rangeOfString:@"@3x."];
                if (range.location != NSNotFound) {
                    scale = 3.0;
                }
            }
            //生成缩放后的图片 然后返回
            UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
            image = scaledImage;
        }
        return image;
    }
}

根据以上代码,根据传入的key中的关键信息来对源图片进行比例放大以获取对应大小的图片。

SDCacheCostForImage计算空间花销

NSCache提供一套类似于字典的key/value方式来进行存取内部对象。

- (nullable ObjectType)objectForKey:(KeyType)key;
- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;

@property NSUInteger totalCostLimit;    // limits are imprecise/not strict
@property NSUInteger countLimit;    // limits are imprecise/not strict

使用方法类似 NSDictionary。可以通过设置 NSCache能占用的最大空间花销totalCostLimit或者最大对象缓存数量countLimit。比如我们设置缓存最多占用20mb,然后每次存入缓存图片时将图片大小作为cost参数传入,当缓存大小或数量超过限定值时,内部的缓存机制就会自动为我们执行清理操作而且NSCache是线程安全的。
同样的,在SDImageCache中提供了两个与此对应的属性用于管理NSCache的这个特性。

/**
 * The maximum "total cost" of the in-memory image cache. The cost function is the number of pixels held in memory.
 */
@property (assign, nonatomic) NSUInteger maxMemoryCost;

/**
 * The maximum number of objects the cache should hold.
 */
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;

但是需要注意的是:在SDWebImage内部,并没有任何代码显式的为内存缓存AutoPurgeCache(前面已经提过,这是继承于NSCache的一个子类)设置最大空间花销和最大缓存对象数量,除非使用者(我们)为这个类的以上两个属性赋值。

前面举的例子中,在setObject方法中传入cost的是该对象所占用的内存大小,即字节数的多少。但是是否在本框架中,也使用同样的计算机制呢。SDImageCache类中,调用下面的C函数进行计算cost参数。

//为图片计算空间花销 以像素点多少为单位
//FOUNDATION_STATIC_INLINE 为 系统定义的宏 (== static inline) 内联函数定义
//C函数
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
    return image.size.height * image.size.width * image.scale * image.scale;
}

因此我们可以看到,SDImageCache中计算占用内存大小的方法并不是单纯使用字节数为单位,而是以像素点的个数为单位进行计算。这一点在一些博客中并没有提及或者错误的说明了。

流程图总结

接下来用一个流程图总结一下本小节内容。


SDImageCache请求缓存.png

图片缓存

在了解缓存获取的设计之后,图片缓存模块的设计与其相近。话不多说,直接上代码。

storeImage方法实现

/**
 * Asynchronously store an image into memory and disk cache at the given key.
 *
 * @param image           The image to store
 * @param imageData       The image data as returned by the server, this representation will be used for disk storage
 *                        instead of converting the given image object into a storable/compressed image format in order
 *                        to save quality and CPU
 * @param key             The unique image cache key, usually it's image absolute URL
 * @param toDisk          Store the image to disk cache if YES
 * @param completionBlock A block executed after the operation is finished
 */
- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    if (!image || !key) {
        //image和key都为nil 执行block 返回
        if (completionBlock) {
            completionBlock();
        }
        return;
    }
    // if memory cache is enabled
    //1. 如果设置了需要进行memory cache 将图片缓存到内存
    
    if (self.config.shouldCacheImagesInMemory) {
        //1.1 计算空间开销
        NSUInteger cost = SDCacheCostForImage(image);
        //1.2 缓存到NSCache中
        [self.memCache setObject:image forKey:key cost:cost];
    }
    //2. 如果需要缓存到磁盘中
    if (toDisk) {
        // 在ioQueue 异步缓存
        dispatch_async(self.ioQueue, ^{
            @autoreleasepool {
                NSData *data = imageData;
                if (!data && image) {
                    //data为空 则需要计算data
                    //2.1 根据data获取SDImageFormat 这是一个 枚举类型
                    SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
                    //2.2 根据图片格式生成data
                    data = [image sd_imageDataAsFormat:imageFormatFromData];
                }
                //2.3 将图片数据存储到磁盘中,以key为索引
                [self storeImageDataToDisk:data forKey:key];
            }
            //3.主线程执行回调
            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    } else {//3.不需要进行磁盘缓存,直接执行回调
        if (completionBlock) {
            completionBlock();
        }
    }
}

缓存的基本过程已经在注释写清楚。核心方法是调用:

- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;

进行磁盘缓存。

storeImageDataToDisk方法

storeImageDataToDisk方法将key(通常是URL)和上面方法生成的data缓存到磁盘。

/**
 * Synchronously store image NSData into disk cache at the given key.
 *
 * @warning This method is synchronous, make sure to call it from the ioQueue
 *
 * @param imageData  The image data to store
 * @param key        The unique image cache key, usually it's image absolute URL
 */

- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
    if (!imageData || !key) {
        return;
    }
    //确保该方法在ioQueue中同步地执行
    [self checkIfQueueIsIOQueue];
    //判断缓存文件路径是否存在,如果不存在则使用fileManager新建
    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:imageData attributes:nil];
    
    // disable iCloud backup
    if (self.config.shouldDisableiCloud) {
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

这个方法需要在串行队列ioQueue中同步执行,主要任务就是新建存储图片数据的文件夹,并使用[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil]imageData写入该路径下。

补充:缓存文件名

磁盘缓存使用了传入的key的MD5转换之后的结果作为该图片的磁盘缓存文件名。

- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
    const char *str = key.UTF8String;
    if (str == NULL) {
        str = "";
    }
    //开辟一个16字节的空间 
    //#define CC_MD5_DIGEST_LENGTH    16          /* digest length in bytes */
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    //执行加密
    CC_MD5(str, (CC_LONG)strlen(str), r);
    //转换为字符串 x% 为16进制
    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;
}

缓存清理

在SDWebImage框架下,缓存清理情况分为两种:

  1. Cache clear 即清除所有缓存。
  2. Delete old files 即整理缓存空间。

第一种情况比较简单,直接删除所有缓存数据即可。

#pragma mark - Cache clean Ops
//1. 清理内存
- (void)clearMemory {
    [self.memCache removeAllObjects];
}
//2. 清理磁盘
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
    dispatch_async(self.ioQueue, ^{
        //2.1 删除缓存目录下所有文件
        [_fileManager removeItemAtPath:self.diskCachePath error:nil];
       //2.2 新建一个同名文件夹
        [_fileManager createDirectoryAtPath:self.diskCachePath
                withIntermediateDirectories:YES
                                 attributes:nil
                                      error:NULL];

        if (completion) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion();
            });
        }
    });
}

第二种情况用于整理磁盘缓存文件。

- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        /**
         这里给出key对应的信息
         NSURLContentModificationDateKey-> The time the resource content was last modified (Read-write, value type NSDate)
         NSURLIsDirectoryKey -> True for directories (Read-only, value type boolean NSNumber)
         NSURLTotalFileAllocatedSizeKey -> Total allocated size of the file in bytes (this may include space used by metadata), or nil if not available. (Read-only, value type NSNumber)
         */
        NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
        //初始化enumerator,在后续会遍历diskCachePath目录下的文件,通过resoureceKeys获取相关属性
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];
        //获取config类中调用者设置的最大缓存寿命
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
        NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
        NSUInteger currentCacheSize = 0;

        //遍历cache directory下所有文件,并进行以下操作:
        //1. 移除所有生成日期早于expirationDate的文件
        //2. 保存文件大小相关的属性,用于基于文件大小的缓存清理操作
        
        //初始化一个数组保存要移除的文件url
        NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
        for (NSURL *fileURL in fileEnumerator) {
            NSError *error;
            //1. 提取出与resourceKeys对应的值
            NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];

            //2. 跳过目录和错误情况
            if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;
            }

            //3. 记录所有生成日期早于expirationDate的文件
            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [urlsToDelete addObject:fileURL];
                continue;
            }
            //4. 记录文件相关属性 保存在cacheFiles字典中 url -> key/value (string -> id)
            //currentCacheSize记录当前目录下所有缓存文件的大小
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
            cacheFiles[fileURL] = resourceValues;
        }
        //5. 清除第三步记录的过期文件
        for (NSURL *fileURL in urlsToDelete) {
            [_fileManager removeItemAtURL:fileURL error:nil];
        }
        // 6. 如果currentCacheSize > config中配置的最大文件大小
        // 执行第二步清理操作,首先清理最早被缓存的文件
        if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
            // Target half of our maximum cache size for this cleanup pass.
            // 6.1 清理的目标为最大缓存大小的一半
            const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;

            // 6.2 按文件的最后修改时间进行排序(旧文件在前)
            NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                                     usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                         return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                                     }];

            // 6.3 按排序数组从前往后删除文件 直到清理目标大小
            for (NSURL *fileURL in sortedFiles) {
                if ([_fileManager removeItemAtURL:fileURL error:nil]) {
                    NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                    currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;

                    if (currentCacheSize < desiredCacheSize) {
                        //达到目标 终止循环
                        break;
                    }
                }
            }
        }
        // 7. 清理完成 主线程执行回调
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
}

SDImageCache首先清理过期文件。如果设置了最大缓存空间config.maxCacheSize,且清理完过期文件后发现占用的磁盘大小仍大于self.config.maxCacheSize,则对文件按照其修改日期的先后进行排序,旧文件排在前面。最后从排序数组中根据其URL一个一个从磁盘中移除,直到

currentCacheSize < 0.5 * maxCacheSize;

缓存清理的时机

这个时候我们再回头看SDImageCache的全能初始化方法中注册通知监听系统通知的代码。

       [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(deleteOldFiles)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundDeleteOldFiles)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];

缓存清理的时机有如下几个:

  1. 接收到UIApplicationDidReceiveMemoryWarningNotification 内存警告通知时,清除所有内存缓存。
  2. 接收到name:UIApplicationWillTerminateNotification 应用即将被关闭通知时,整理磁盘缓存。
  3. 接收到name:UIApplicationWillTerminateNotification 应用即将进入后台通知时,在后台整理磁盘缓存。

总结

SDWebImage的缓存模块本文章大致总结到这里,主要的功能和函数都给出。篇幅较长,也说明了本模块的重要性,同样是找工作面试常常会问到的地方。尽管如此,本模块的核心逻辑非常简单:先内存后磁盘(如有没有额外设置的情况下)。无论是获取缓存图片还是将图片缓存。
与缓存类SDImageCache配合使用的还有SDImageCacheConfig类,用于配置与缓存的相关信息,例如最大缓存数量等。
下一篇将对SDWebImage的图片解码器进行解析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容