讀碼筆記-YYWebImage源碼 (三) -YYImageCache

這個類內(nèi)容比較簡單,api也都 通俗易懂,僅僅把注釋貼上來即可,深究為什么這么簡單,其實還是因為這個類做的事情其實僅僅是統(tǒng)一的調(diào)用YYMemoryCache,YYMemoryCache來存取圖片而已,具體的存儲細(xì)節(jié)都實現(xiàn)在YYCache里面了,所以這里使用起來才會簡單輕松.

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

///圖片緩存類型
typedef NS_OPTIONS(NSUInteger, YYImageCacheType) {
    /// No value.
    YYImageCacheTypeNone   = 0,
    
    /// Get/store image with memory cache.//從內(nèi)存中獲取
    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數(shù)據(jù)的緩存,是基于內(nèi)存緩存與磁盤緩存實現(xiàn)的
 
 @discussion 磁盤緩存會嘗試保護(hù)原始的圖片數(shù)據(jù)
 如果原始的圖片仍是image,會保存為一個png或者jpeg
 如果原始圖片是一個gif,apng,webp動圖,會保存為原始格式
 如果原始圖片縮放比例不是1,那么縮放值會被保存為一個縮放的數(shù)據(jù)
 雖然圖片能被NSCoding協(xié)議解碼,但是這不是一個最優(yōu)解:
 蘋果的確使用UIImagePNGRepresentation()來解碼所有類型的圖片,但是可能會丟失原始的可變幀數(shù)據(jù).結(jié)果就是打包成plist文件不能直接查看照片.如果圖片沒有alpha通道,使用JPEG代理PNG能夠保存更多的尺寸和編解碼時間.
 */
@interface YYImageCache : NSObject

//緩存名字,默認(rèn)為nil
@property (copy) NSString *name;

//內(nèi)存緩存,具體信息看YYMemoryCache
@property (strong, readonly) YYMemoryCache *memoryCache;

//磁盤緩存,具體信息看YYDiskCache
@property (strong, readonly) YYDiskCache *diskCache;

/**
 *  當(dāng)從磁盤緩存請求圖片的時候是否解碼動圖,默認(rèn)為YES
 @discussion 當(dāng)從磁盤緩存讀取圖片,會使用YYImage來解碼比如WebP/APNG/GIF格式的動圖,設(shè)置這個值為NO可以忽略動圖
 */
@property (assign) BOOL allowAnimatedImage;

/**
 *  是否解碼圖片存儲位圖,默認(rèn)為YES
 @discussion 如果這個值為YES,圖片會通過位圖解碼來獲得更好的用戶體驗,但是可能會消耗更大的內(nèi)存資源
 */
@property (assign) BOOL decodeForDisplay;


- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;

/**
 *  單例類初始化方法
 */
+ (instancetype)sharedCache;

/**
 *  初始化方法,在多個情況下訪問同一個路徑會導(dǎo)致緩存不穩(wěn)定
 *
 *  @param path cache讀寫的全路徑,只初始化一次,你不應(yīng)該來讀寫這個路徑
 *
 *  @return 一個新的緩存對象,或者返回帶nil帶error信息
 */
- (instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;


/**
 *  把圖片通過一個具體的key存進(jìn)緩存,同時memory跟disk都會存,這個方法會立刻返回,在后臺線程執(zhí)行
 *
 *  @param image 如果為nil這個方法無效
 *  @param key 存儲圖片的key,為nil這個方法無效
 */
- (void)setImage:(UIImage *)image forKey:(NSString *)key;

/**
 *  通過一個key把圖片緩存,這個方法會立刻返回并在后臺執(zhí)行
    如果'type'包括'YYImageCacheTypeMemory',那么圖片會被存進(jìn)memory,如果image為nil會用'imageData'代理
    如果'type'包括'YYImageCacheTypeDisk',那么'imageData'會被存進(jìn)磁盤緩存,如果'imageData'為nil會用image代替
 //這里可以看到作者一個思想,如果存進(jìn)memory,直接存image,會減小很多解碼的消耗,如果存disk,會存imageData
 *
 */
- (void)setImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key withType:(YYImageCacheType)type;

/**
 *  通過key移除cache中的一個圖片,memory跟disk會同時移除
    這個方法會立刻返回并在后臺線程執(zhí)行
 *
 *  @param key 移除圖片用的key,為nil的話這個方法沒啥用
 */
- (void)removeImageForKey:(NSString *)key;

/**
 *  從緩存中通過key刪圖片
 這個方法會立刻返回并在后臺線程執(zhí)行
 *
 *  @param key  key
 *  @param type 從哪刪除,跟上個方法不同,這個可以刪除指定類型的緩存
 */
- (void)removeImageForKey:(NSString *)key withType:(YYImageCacheType)type;

/**
 *  通過key檢查緩存中是否有某個圖片
    如果圖片不在內(nèi)存中,這個方法可能會阻塞線程,知道這個文件讀取完畢
 *
 *  @param key key,為nil時返回NO
 *
 */
- (BOOL)containsImageForKey:(NSString *)key;

/**
 *  跟上個差不多,只不過可以查具體類型的緩存
 */
- (BOOL)containsImageForKey:(NSString *)key withType:(YYImageCacheType)type;

/**
 *  通過key獲取圖片,如果圖片不在內(nèi)存中,這個方法可能會阻塞線程知道文件讀取完畢
 *
 *  @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回調(diào),主線程調(diào)用的
 */
- (void)getImageForKey:(NSString *)key withType:(YYImageCacheType)type withBlock:(void(^)(UIImage *image, YYImageCacheType type))block;

/**
 *  通過key查找圖片數(shù)據(jù)data格式,方法會阻塞主線程知道文件讀取完畢
 *
 *  @param key key
 *
 *  @return 圖片數(shù)據(jù),查不到為nil
 */
- (NSData *)getImageDataForKey:(NSString *)key;

/**
 *  通過key來異步的獲取圖片數(shù)據(jù)
 *
 *  @param key   <#key description#>
 *  @param block 主線程的完成回調(diào)
 */
- (void)getImageDataForKey:(NSString *)key withBlock:(void(^)(NSData *imageData))block;

其實現(xiàn)細(xì)節(jié)如下:


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轉(zhuǎn)換為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];
}

/**
 *  在初始化的時候同時初始化內(nèi)存緩存跟磁盤緩存
 *
 */
- (instancetype)initWithPath:(NSString *)path {
    //在調(diào)用父類init之前先初始化一個內(nèi)存緩存跟磁盤緩存
    YYMemoryCache *memoryCache = [YYMemoryCache new];//生成內(nèi)存緩存
    memoryCache.shouldRemoveAllObjectsOnMemoryWarning = YES;//內(nèi)存警告的時候刪除所有內(nèi)容
    memoryCache.shouldRemoveAllObjectsWhenEnteringBackground = YES;//進(jìn)入后臺刪除所有內(nèi)容
    memoryCache.countLimit = NSUIntegerMax;//不予限制
    memoryCache.costLimit = NSUIntegerMax;//不予限制
    memoryCache.ageLimit = 12 * 60 * 60;//cache存在的時間限制設(shè)置為12個小時
    
    YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path];//生成磁盤緩存
    diskCache.customArchiveBlock = ^(id object) { return (NSData *)object; };//自己來archive數(shù)據(jù)
    diskCache.customUnarchiveBlock = ^(NSData *data) { return (id)data; };//自己unarchive數(shù)據(jù)
    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 {
    //在每一個方法執(zhí)行前先檢查參數(shù)的有效性,非常好的習(xí)慣
    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) {
                //開啟了位圖解碼的話直接把圖片丟進(jìn)內(nèi)存緩存里面咯
                [_memoryCache setObject:image forKey:key withCost:[_self imageCost:image]];
            } else {
                //否則開啟一個異步的解碼隊列,把圖片轉(zhuǎn)成位圖,再丟進(jìn)緩存里面
                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) {//如果圖片不存在,圖片數(shù)據(jù)存在,那就通過data生成一個圖片,丟進(jìn)內(nèi)存中存起來
            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]];
            });
        }
    }
    //如果類型包含磁盤緩存,存進(jìn)磁盤
    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;
}

//跟上個方法類似,只不過把查詢的結(jié)果通過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地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晒杈,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子孔厉,更是在濱河造成了極大的恐慌拯钻,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撰豺,死亡現(xiàn)場離奇詭異粪般,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)污桦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門亩歹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凡橱,你說我怎么就攤上這事小作。” “怎么了稼钩?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵顾稀,是天一觀的道長。 經(jīng)常有香客問我坝撑,道長静秆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任巡李,我火速辦了婚禮诡宗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘击儡。我一直安慰自己,他們只是感情好蝠引,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布阳谍。 她就那樣靜靜地躺著,像睡著了一般螃概。 火紅的嫁衣襯著肌膚如雪矫夯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天吊洼,我揣著相機(jī)與錄音训貌,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛递沪,可吹牛的內(nèi)容都是我干的豺鼻。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼款慨,長吁一口氣:“原來是場噩夢啊……” “哼儒飒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起檩奠,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤桩了,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后埠戳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體井誉,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年整胃,在試婚紗的時候發(fā)現(xiàn)自己被綠了颗圣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡爪模,死狀恐怖欠啤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情屋灌,我是刑警寧澤洁段,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站共郭,受9級特大地震影響祠丝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜除嘹,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一写半、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尉咕,春花似錦叠蝇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至单芜,卻和暖如春蜕该,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洲鸠。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工堂淡, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留馋缅,地道東北人。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓绢淀,卻偏偏與公主長得像萤悴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子更啄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫稚疹、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,124評論 4 61
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,331評論 25 707
  • 當(dāng)城市的燈光快速地從公交車的身邊閃退祭务,留在記憶里只有冰冷的建筑的輪廓内狗。公交到站,上車下車的陌生人沉迷于手機(jī)义锥,隨意...
    初學(xué)者Ivy閱讀 203評論 0 0
  • 永不惡言相向 永不暗自考量 永不放任乖張 永不停止成長 為你追風(fēng)逐浪 為你再次瘋狂 為你永存想像 為你逃過死亡 永...
    七八立里閱讀 4,621評論 0 0
  • 我叫小楠楠柳沙,今年22歲,在我13歲的那一年拌倍,交了第一個男朋友赂鲤,他叫亮亮,是一個混混型的同學(xué)柱恤,是我的同學(xué)給我介紹的数初,...
    筽卡里閱讀 350評論 0 0