32

SDWebImage学习笔记之SDImageCache

 5 years ago
source link: http://www.cocoachina.com/ios/20180710/24085.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

SDMemoryCache

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

// strong-weak cache
@property (nonatomic, strong, nonnull) NSMapTable*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 (assign, nonatomic) BOOL shouldDecompressImages;
// 是否禁用iCloud
@property (assign, nonatomic) BOOL shouldDisableiCloud;
// 是否使用内存缓存,默认YES
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
// 磁盘缓存读取选项,枚举
@property (assign, nonatomic) NSDataReadingOptions diskCacheReadingOptions;
// 磁盘缓存写入选项,枚举
@property (assign, nonatomic) NSDataWritingOptions diskCacheWritingOptions;
// 在缓存中保存图像的最长时间,以秒为单位
@property (assign, nonatomic) NSInteger maxCacheAge;
// 缓存的最大大小,以字节为单位
@property (assign, nonatomic) NSUInteger maxCacheSize;
// 缓存配置过期类型,枚举
@property (assign, nonatomic) 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。

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK