這個類內(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