读码笔记-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地址

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,825评论 25 707
  • 当城市的灯光快速地从公交车的身边闪退,留在记忆里只有冰冷的建筑的轮廓。公交到站,上车下车的陌生人沉迷于手机,随意...
    初学者Ivy阅读 200评论 0 0
  • 永不恶言相向 永不暗自考量 永不放任乖张 永不停止成长 为你追风逐浪 为你再次疯狂 为你永存想像 为你逃过死亡 永...
    七八立里阅读 4,586评论 0 0
  • 我叫小楠楠,今年22岁,在我13岁的那一年,交了第一个男朋友,他叫亮亮,是一个混混型的同学,是我的同学给我介绍的,...
    筽卡里阅读 332评论 0 0