打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
读码笔记-YYWebImage源码 (三) -YYImageCache

读码笔记-YYWebImage源码 (三) -YYImageCache

这个类内容比较简单,api也都 通俗易懂,仅仅把注释贴上来即可,深究为什么这么简单,其实还是因为这个类做的事情其实仅仅是统一的调用YYMemoryCache,YYMemoryCache来存取图片而已,具体的存储细节都实现在YYCache里面了,所以这里使用起来才会简单轻松.

先看开放了哪些api,都是什么意思

///图片缓存类型typedef NS_OPTIONS(NSUInteger, YYImageCacheType) {      /// No value.    YYImageCacheTypeNone   = 0,    /// Get/store image with memory cache.//从内存中获取    YYImageCacheTypeMemory = 1 << 0,    /// Get/store image with disk cache.//从磁盘中获取    YYImageCacheTypeDisk   = 1 << 1,    /// Get/store image with both memory cache and disk cache.//同时获取    YYImageCacheTypeAll    = YYImageCacheTypeMemory | YYImageCacheTypeDisk,};/** *  YYImageCache是一个用来存储UIImage和image数据的缓存,是基于内存缓存与磁盘缓存实现的 @discussion 磁盘缓存会尝试保护原始的图片数据 如果原始的图片仍是image,会保存为一个png或者jpeg 如果原始图片是一个gif,apng,webp动图,会保存为原始格式 如果原始图片缩放比例不是1,那么缩放值会被保存为一个缩放的数据 虽然图片能被NSCoding协议解码,但是这不是一个最优解: 苹果的确使用UIImagePNGRepresentation()来解码所有类型的图片,但是可能会丢失原始的可变帧数据.结果就是打包成plist文件不能直接查看照片.如果图片没有alpha通道,使用JPEG代理PNG能够保存更多的尺寸和编解码时间. */@interface YYImageCache : NSObject//缓存名字,默认为nil@property (copy) NSString *name;//内存缓存,具体信息看YYMemoryCache@property (strong, readonly) YYMemoryCache *memoryCache;//磁盘缓存,具体信息看YYDiskCache@property (strong, readonly) YYDiskCache *diskCache;/** *  当从磁盘缓存请求图片的时候是否解码动图,默认为YES @discussion 当从磁盘缓存读取图片,会使用YYImage来解码比如WebP/APNG/GIF格式的动图,设置这个值为NO可以忽略动图 */@property (assign) BOOL allowAnimatedImage;/** *  是否解码图片存储位图,默认为YES @discussion 如果这个值为YES,图片会通过位图解码来获得更好的用户体验,但是可能会消耗更大的内存资源 */@property (assign) BOOL decodeForDisplay;- (instancetype)init UNAVAILABLE_ATTRIBUTE;+ (instancetype)new UNAVAILABLE_ATTRIBUTE;/** *  单例类初始化方法 */+ (instancetype)sharedCache;/** *  初始化方法,在多个情况下访问同一个路径会导致缓存不稳定 * *  @param path cache读写的全路径,只初始化一次,你不应该来读写这个路径 * *  @return 一个新的缓存对象,或者返回带nil带error信息 */- (instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;/** *  把图片通过一个具体的key存进缓存,同时memory跟disk都会存,这个方法会立刻返回,在后台线程执行 * *  @param image 如果为nil这个方法无效 *  @param key 存储图片的key,为nil这个方法无效 */- (void)setImage:(UIImage *)image forKey:(NSString *)key;/** *  通过一个key把图片缓存,这个方法会立刻返回并在后台执行    如果'type'包括'YYImageCacheTypeMemory',那么图片会被存进memory,如果image为nil会用'imageData'代理    如果'type'包括'YYImageCacheTypeDisk',那么'imageData'会被存进磁盘缓存,如果'imageData'为nil会用image代替 //这里可以看到作者一个思想,如果存进memory,直接存image,会减小很多解码的消耗,如果存disk,会存imageData * */- (void)setImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key withType:(YYImageCacheType)type;/** *  通过key移除cache中的一个图片,memory跟disk会同时移除    这个方法会立刻返回并在后台线程执行 * *  @param key 移除图片用的key,为nil的话这个方法没啥用 */- (void)removeImageForKey:(NSString *)key;/** *  从缓存中通过key删图片 这个方法会立刻返回并在后台线程执行 * *  @param key  key *  @param type 从哪删除,跟上个方法不同,这个可以删除指定类型的缓存 */- (void)removeImageForKey:(NSString *)key withType:(YYImageCacheType)type;/** *  通过key检查缓存中是否有某个图片    如果图片不在内存中,这个方法可能会阻塞线程,知道这个文件读取完毕 * *  @param key key,为nil时返回NO * */- (BOOL)containsImageForKey:(NSString *)key;/** *  跟上个差不多,只不过可以查具体类型的缓存 */- (BOOL)containsImageForKey:(NSString *)key withType:(YYImageCacheType)type;/** *  通过key获取图片,如果图片不在内存中,这个方法可能会阻塞线程知道文件读取完毕 * *  @param key 一个字符串类型图片缓存key,为nil方法返回nil * *  @return 通过key查到的图片,没有图片就是nil */- (UIImage *)getImageForKey:(NSString *)key;/** *  跟上个方法差不多,只不过从指定缓存类型中获取图片 */- (UIImage *)getImageForKey:(NSString *)key withType:(YYImageCacheType)type;/** *  通过key异步的获取图片 * *  @param key   key *  @param type  缓存类型 *  @param block 完成的block回调,主线程调用的 */- (void)getImageForKey:(NSString *)key withType:(YYImageCacheType)type withBlock:(void(^)(UIImage *image, YYImageCacheType type))block;/** *  通过key查找图片数据data格式,方法会阻塞主线程知道文件读取完毕 * *  @param key key * *  @return 图片数据,查不到为nil */- (NSData *)getImageDataForKey:(NSString *)key;/** *  通过key来异步的获取图片数据 * *  @param key   <#key description#> *  @param block 主线程的完成回调 */- (void)getImageDataForKey:(NSString *)key withBlock:(void(^)(NSData *imageData))block;

其实现细节如下:

static inline dispatch_queue_t YYImageCacheIOQueue() {  #ifdef YYDispatchQueuePool_h    return YYDispatchQueueGetForQOS(NSQualityOfServiceDefault);#else    return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);#endif}static inline dispatch_queue_t YYImageCacheDecodeQueue() {  #ifdef YYDispatchQueuePool_h    return YYDispatchQueueGetForQOS(NSQualityOfServiceUtility);#else    return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);#endif}@interface YYImageCache ()- (NSUInteger)imageCost:(UIImage *)image;- (UIImage *)imageFromData:(NSData *)data;@end@implementation YYImageCache/** *  图片消耗 */- (NSUInteger)imageCost:(UIImage *)image {    CGImageRef cgImage = image.CGImage;    if (!cgImage) return 1;    CGFloat height = CGImageGetHeight(cgImage);    size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);    NSUInteger cost = bytesPerRow * height;    if (cost == 0) cost = 1;    return cost;}/** *  通过data转换为image */- (UIImage *)imageFromData:(NSData *)data {    NSData *scaleData = [YYDiskCache getExtendedDataFromObject:data];    CGFloat scale = 0;    if (scaleData) {        scale = ((NSNumber *)[NSKeyedUnarchiver unarchiveObjectWithData:scaleData]).doubleValue;    }    if (scale <= 0) scale = [UIScreen mainScreen].scale;    UIImage *image;    if (_allowAnimatedImage) {        image = [[YYImage alloc] initWithData:data scale:scale];        if (_decodeForDisplay) image = [image yy_imageByDecoded];    } else {        YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];        image = [decoder frameAtIndex:0 decodeForDisplay:_decodeForDisplay].image;    }    return image;}#pragma mark Public/** *  单例类的初始化方法 */+ (instancetype)sharedCache {    static YYImageCache *cache = nil;    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,                                                                   NSUserDomainMask, YES) firstObject];        //拼接路径        cachePath = [cachePath stringByAppendingPathComponent:@"com.ibireme.yykit"];        cachePath = [cachePath stringByAppendingPathComponent:@"images"];        cache = [[self alloc] initWithPath:cachePath];    });    return cache;}- (instancetype)init {    @throw [NSException exceptionWithName:@"YYImageCache init error" reason:@"YYImageCache must be initialized with a path. Use 'initWithPath:' instead." userInfo:nil];    return [self initWithPath:nil];}/** *  在初始化的时候同时初始化内存缓存跟磁盘缓存 * */- (instancetype)initWithPath:(NSString *)path {    //在调用父类init之前先初始化一个内存缓存跟磁盘缓存    YYMemoryCache *memoryCache = [YYMemoryCache new];//生成内存缓存    memoryCache.shouldRemoveAllObjectsOnMemoryWarning = YES;//内存警告的时候删除所有内容    memoryCache.shouldRemoveAllObjectsWhenEnteringBackground = YES;//进入后台删除所有内容    memoryCache.countLimit = NSUIntegerMax;//不予限制    memoryCache.costLimit = NSUIntegerMax;//不予限制    memoryCache.ageLimit = 12 * 60 * 60;//cache存在的时间限制设置为12个小时    YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path];//生成磁盘缓存    diskCache.customArchiveBlock = ^(id object) { return (NSData *)object; };//自己来archive数据    diskCache.customUnarchiveBlock = ^(NSData *data) { return (id)data; };//自己unarchive数据    if (!memoryCache || !diskCache) return nil;//如果有任意一个初始化失败,返回nil    self = [super init];    _memoryCache = memoryCache;    _diskCache = diskCache;    _allowAnimatedImage = YES;    _decodeForDisplay = YES;    return self;}- (void)setImage:(UIImage *)image forKey:(NSString *)key {    [self setImage:image imageData:nil forKey:key withType:YYImageCacheTypeAll];}- (void)setImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key withType:(YYImageCacheType)type {    //在每一个方法执行前先检查参数的有效性,非常好的习惯    if (!key || (image == nil && imageData.length == 0)) return;    __weak typeof(self) _self = self;    //如果类型有YYImageCacheTypeMemory    if (type & YYImageCacheTypeMemory) { // add to memory cache        if (image) {            if (image.yy_isDecodedForDisplay) {                //开启了位图解码的话直接把图片丢进内存缓存里面咯                [_memoryCache setObject:image forKey:key withCost:[_self imageCost:image]];            } else {                //否则开启一个异步的解码队列,把图片转成位图,再丢进缓存里面                dispatch_async(YYImageCacheDecodeQueue(), ^{                    __strong typeof(_self) self = _self;                    if (!self) return;                    [self.memoryCache setObject:[image yy_imageByDecoded] forKey:key withCost:[self imageCost:image]];                });            }        } else if (imageData) {//如果图片不存在,图片数据存在,那就通过data生成一个图片,丢进内存中存起来            dispatch_async(YYImageCacheDecodeQueue(), ^{                __strong typeof(_self) self = _self;                if (!self) return;                UIImage *newImage = [self imageFromData:imageData];                [self.memoryCache setObject:[self imageFromData:imageData] forKey:key withCost:[self imageCost:newImage]];            });        }    }    //如果类型包含磁盘缓存,存进磁盘    if (type & YYImageCacheTypeDisk) { // add to disk cache        if (imageData) {            if (image) {                [YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:imageData];            }            [_diskCache setObject:imageData forKey:key];        } else if (image) {            dispatch_async(YYImageCacheIOQueue(), ^{                __strong typeof(_self) self = _self;                if (!self) return;                NSData *data = [image yy_imageDataRepresentation];                [YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:data];                [self.diskCache setObject:data forKey:key];            });        }    }}/** *  全删咯 * */- (void)removeImageForKey:(NSString *)key {    [self removeImageForKey:key withType:YYImageCacheTypeAll];}//有哪个类型删哪个- (void)removeImageForKey:(NSString *)key withType:(YYImageCacheType)type {    if (type & YYImageCacheTypeMemory) [_memoryCache removeObjectForKey:key];    if (type & YYImageCacheTypeDisk) [_diskCache removeObjectForKey:key];}- (BOOL)containsImageForKey:(NSString *)key {    return [self containsImageForKey:key withType:YYImageCacheTypeAll];}- (BOOL)containsImageForKey:(NSString *)key withType:(YYImageCacheType)type {    if (type & YYImageCacheTypeMemory) {        if ([_memoryCache containsObjectForKey:key]) return YES;    }    if (type & YYImageCacheTypeDisk) {        if ([_diskCache containsObjectForKey:key]) return YES;    }    return NO;}- (UIImage *)getImageForKey:(NSString *)key {    return [self getImageForKey:key withType:YYImageCacheTypeAll];}//通过key找图片,都比较简单- (UIImage *)getImageForKey:(NSString *)key withType:(YYImageCacheType)type {    if (!key) return nil;    if (type & YYImageCacheTypeMemory) {        UIImage *image = [_memoryCache objectForKey:key];        if (image) return image;    }    if (type & YYImageCacheTypeDisk) {        NSData *data = (id)[_diskCache objectForKey:key];        UIImage *image = [self imageFromData:data];        if (image && (type & YYImageCacheTypeMemory)) {            [_memoryCache setObject:image forKey:key withCost:[self imageCost:image]];        }        return image;    }    return nil;}//跟上个方法类似,只不过把查询的结果通过block传递了回去- (void)getImageForKey:(NSString *)key withType:(YYImageCacheType)type withBlock:(void (^)(UIImage *image, YYImageCacheType type))block {    if (!block) return;    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{        UIImage *image = nil;        if (type & YYImageCacheTypeMemory) {            image = [_memoryCache objectForKey:key];            if (image) {                dispatch_async(dispatch_get_main_queue(), ^{                    block(image, YYImageCacheTypeMemory);                });                return;            }        }        if (type & YYImageCacheTypeDisk) {            NSData *data = (id)[_diskCache objectForKey:key];            image = [self imageFromData:data];            if (image) {                [_memoryCache setObject:image forKey:key];                dispatch_async(dispatch_get_main_queue(), ^{                    block(image, YYImageCacheTypeDisk);                });                return;            }        }        dispatch_async(dispatch_get_main_queue(), ^{            block(nil, YYImageCacheTypeNone);        });    });}- (NSData *)getImageDataForKey:(NSString *)key {    return (id)[_diskCache objectForKey:key];}- (void)getImageDataForKey:(NSString *)key withBlock:(void (^)(NSData *imageData))block {    if (!block) return;    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{        NSData *data = (id)[_diskCache objectForKey:key];        dispatch_async(dispatch_get_main_queue(), ^{            block(data);        });    });}@end

PS:
YYWebImage源码地址
我fork下来添加注释的版本github地址

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
保存UIImage到文件
AFNetworking 文件上传Data,File图片,文件等上传(转)
11、SDWebImage中那些好用的方法
IOS中UITableView异步加载图片的实现
Swift之一步一步带你封装一个本地缓存库
Objective-C的缓存框架EGOCache在iOS App开发中的使用
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服