转载

SDWebImage学习笔记之SDImageCache

SDMemoryCache

SDMemoryCache是SDImageCache类中的一个私有类,继承自NSCache类,它接收两个泛型用于定义NSMapTable类型的属性weakCache。

// strong-weak cache
@property (nonatomicstrongnonnullNSMapTable*weakCache; 

NSMapTable在SDWebImage学习笔记之NSMapTable中做过介绍,weakCache属性的初始化代码为

self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];

表示weakCache变量指向的对象强引用key值,弱引用value值。假如没有其他变量强引用value值,weakCache变量将安全的删除相应的key-value。

SDMemoryCache还定义了一个dispatch_semaphore_t属性weakCacheLock。

// a lock to keep the access to `weakCache` thread-safe
@property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; 

weakCacheLock属性创建了一个初始值为1的信号量,表示同时最多只有一个线程可以访问资源,初始化代码为:

self.weakCacheLock = dispatch_semaphore_create(1);

SDMemoryCache重写了父类NSCache的三个方法:

  1. -(nullable ObjectType)objectForKey:(KeyType)key;

  2. -(void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;

  3. -(void)removeObjectForKey:(KeyType)key;

通过对信号量weakCacheLock的控制,实现线程安全的weakCache赋值、取值、删除操作,还提供了一个清空缓存的函数
-(void)removeAllObjects。

#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#define UNLOCK(lock) dispatch_semaphore_signal(lock);

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

// Store weak cache
LOCK(self.weakCacheLock);
[self.weakCache setObject:obj forKey:key];
UNLOCK(self.weakCacheLock);

// Check weak cache
LOCK(self.weakCacheLock);
obj = [self.weakCache objectForKey:key];
UNLOCK(self.weakCacheLock);
if (obj) {
    // Sync cache
    NSUInteger cost = 0;
    if ([obj isKindOfClass:[UIImage class]]) {
        cost = SDCacheCostForImage(obj);
    }
    [super setObject:obj forKey:key cost:cost];
}

// Remove weak cache
LOCK(self.weakCacheLock);
[self.weakCache removeObjectForKey:key];
UNLOCK(self.weakCacheLock);

// Manually remove should also remove weak cache
LOCK(self.weakCacheLock);
[self.weakCache removeAllObjects];
UNLOCK(self.weakCacheLock);

SDMemoryCache小结

SDMemoryCache类的两个属性:weakCache和weakCacheLock。weakCache用于保存数据,且当数据在外部被销毁时,weakCache可以安全的清除对应的键值对;weakCacheLock用于保证线程安全,同一时刻只允许只允许有一个线程对weakCache进行读写。


SDImageCacheConfig

SDImageCacheConfig用于SDImageCache的配置,继承自NSObject,定义配置属性如下:

// 是否解压图片
@property (assignnonatomicBOOL shouldDecompressImages;
// 是否禁用iCloud
@property (assignnonatomicBOOL shouldDisableiCloud;
// 是否使用内存缓存,默认YES
@property (assignnonatomicBOOL shouldCacheImagesInMemory;
// 磁盘缓存读取选项,枚举
@property (assignnonatomicNSDataReadingOptions diskCacheReadingOptions;
// 磁盘缓存写入选项,枚举
@property (assignnonatomicNSDataWritingOptions diskCacheWritingOptions;
// 在缓存中保存图像的最长时间,以秒为单位
@property (assignnonatomicNSInteger maxCacheAge;
// 缓存的最大大小,以字节为单位
@property (assignnonatomicNSUInteger maxCacheSize;
// 缓存配置过期类型,枚举
@property (assignnonatomic) SDImageCacheConfigExpireType diskCacheExpireType;

SDImageCache

SDImageCache是Cache模块的核心类,它提供了一系列的方法来存储图片,以其中最重要的存储方法为例:

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;

该方法共有6个参数,image表示图片,imageData表示图片数据,在赋值时,代码先判断imageData是否为空,不为空则直接将NSData类型的数据进行存储,如果imageData为空且image不为空,则先判断UIImage类型的图片是否存在Alpha(透明)通道,返回一个SDImageFormat类型的枚举值用于将UIImage转化为NSData,然后进行存储。

// 及时释放图片资源
@autoreleasepool {
    NSData *data = imageData;
    // 判断imageData是否为空
    if (!data && image) {
        // 判断images是否存在Alpha Channel
        SDImageFormat format;
        if (SDCGImageRefContainsAlpha(image.CGImage)) {
            format = SDImageFormatPNG;
        } else {
            format = SDImageFormatJPEG;
        }
        // UIImage -> NSData
        data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
    }
    // 存储data
    [self _storeImageDataToDisk:data forKey:key];
}

第三个参数key有两个作用,一是在内存缓存可用的前提下作为key存储图片,形成映射关系;二是生成磁盘缓存的URL地址,将图片保存在该地址下,后期可以通过key获取地址。

1.
// 内存缓存可用
if (self.config.shouldCacheImagesInMemory) {
    // 计算图片占用空间大小
    NSUInteger cost = SDCacheCostForImage(image);
    // 将key和image作为键值对存储在NSMapTable类型的内存缓存中
    [self.memCache setObject:image forKey:key cost:cost];
}

2.
// _storeImageDataToDisk:forkey:方法
// 使用key生成磁盘缓存地址
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// 将地址转换成NSURL对象
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
// 存储图片
[imageData writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];

第四个参数toDisk用于标示是否需要将图片存储到磁盘,需要的话才会执行存储的操作,第五个参数completionBlock是执行结束的回调,返回值为空。

if (toDisk) {
    // 开启新的线程执行
    dispatch_async(self.ioQueue, ^{
        // 将图片缓存到磁盘
        ......

        if (completionBlock) {
            // 返回主线程执行
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
else {
    if (completionBlock) {
        completionBlock();
    }
}

SDImageCache类中定义了属性ioQueue,它创建了一个串行队列。

_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

串行队列在执行异步操作时,会开启一个新的线程来执行,具体可参考下表:


同步异步
串行队列(主队列)在主线程中执行在主线程中执行
串行队列(非主队列)在当前线程中执行在新建线程中执行
并发队列在当前线程中执行在新建线程中执行

因此在子线程完成存储图片的操作后,需返回到主线程队列执行回调函数。

SDImageCache还提供了查询图片是否存在于磁盘、获取磁盘图片、查询内存图片、删除磁盘图片、删除内存图片等方法,同样,所有的方法都会在串行队列中异步执行,由于串行队列遵守FIFO(先进先出)的原则,所以可以保证只有才一个操作完成后,下一个方法才可以被执行。

当从硬盘中获取图片时,代码会判断内存缓存是否可用,如果可用,则将图片缓存到内存中。

// 获取图片
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
    UIImage *diskImage = [self diskImageForKey:key];
    // 判断图片是否存在及内存缓存是否可用
    if (diskImage && self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(diskImage);
        [self.memCache setObject:diskImage forKey:key cost:cost];
    }
    return diskImage;
}

SDImageCache中定义了一个属性customPaths,可以调用addReadOnlyCachePath:方法往customPaths数组中添加常用的磁盘缓存路径,以便diskImageDataBySearchingAllPathsForKey:方法查找图片资源。

- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
    ......
    NSArray*customPaths = [self.customPaths copy];
    for (NSString *path in customPaths) {
        ......
    }
    ......
}

除了对图片的操作之外,SDImageCache还提供了内存缓存的设置功能,包括设置缓存大小和数量限制,还提供了清理内存缓存、清理磁盘缓存、删除文件的方法,还有一些其他获取图片信息、缓存信息的方法等等。


总结

SDImageCache的核心功能是对图片的存储、查找、删除操作。提供了两种方法用于缓存图片,内存缓存和磁盘缓存。

内存指的是程序的运行空间,空间小但缓存速度快,程序一关闭,内存就被释放了,内存分5大区域:栈区、堆区、全局区、常量区、代码区,由高地址指向低地址。

磁盘指的是程序的存储空间,空间大但缓存速度慢,数据可持久化。
iOS程序的磁盘被称为沙盒,本程序无法访问其他应用的沙盒,沙盒中默认有3个文件夹:Documents, Library 和 tmp。

而且所有的操作都是在串行队列中异步执行,即不会阻塞主线程,也保证了数据的安全性。

正文到此结束
Loading...