YYCache(一)
前言:
咱们一般对网络恳求下来的比较大的数据做缓存,假如没有网络,或者是恳求到的标识和之前的标识共同,表明数据没有变动,则能够运用缓存加载,不需求重新网络拉取数据,这里一般运用YYCache。
从git
上把YYCache pod
下来:
能够看到
YYCache
的文件结构仍是相对简单,除了YYCache
这个对外运用的接口文件,还有YYDiskCache
这个磁盘缓存,YYKVStorage
这个元数据键值存储,还有YYMemoryCache
这个内存缓存。
一、YYCache:
YYCache
是一个线程安全的键值缓存。 它运用YYMemoryCache
将目标存储在一个小而快速的内存缓存中, 并运用YYDiskCache
将目标耐久化到一个大而慢的磁盘缓存中。
特点列表:
特点就三个:
@interface YYCache : NSObject
/** 缓存姓名,只读*/
@property (copy, readonly) NSString *name;
/** 内存缓存.*/
@property (strong, readonly) YYMemoryCache *memoryCache;
/** 磁盘缓存.*/
@property (strong, readonly) YYDiskCache *diskCache;
办法列表:
初始化:
初始化:
/**
用指定的名称创立一个新实例。
*/
- (nullable instancetype)initWithName:(NSString *)name;
/**
用指定的途径创立一个新实例。
*/
- (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
/**
便捷初始化,用指定的名称创立一个新实例。
*/
+ (nullable instancetype)cacheWithName:(NSString *)name;
/**
便捷初始化,用指定的途径创立一个新实例
*/
+ (nullable instancetype)cacheWithPath:(NSString *)path;
拜访办法:
/**
回来一个布尔值,一个指定的键是否在缓存中,可能会堵塞线程直到文件读取完成。
*/
- (BOOL)containsObjectForKey:(NSString *)key;
/**
回来指定键相对应的值。
*/
- (nullable id<NSCoding>)objectForKey:(NSString *)key;
/**
设置缓存中指定键的值。
*/
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;
/**
删去缓存中指定键的值,假如为nil,则此办法无效。
*/
- (void)removeObjectForKey:(NSString *)key;
/**
清空缓存。
*/
- (void)removeAllObjects;
/**
用block清空缓存。能够通过参数得到进度。
*/
- (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
endBlock:(nullable void(^)(BOOL error))end;
看.m
文件的初始化办法initWithName
:
- (instancetype)initWithName:(NSString *)name {
if (name.length == 0) return nil;
NSString *cacheFolder = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
NSString *path = [cacheFolder stringByAppendingPathComponent:name];
return [self initWithPath:path];
}
发现initWithName
会调用另一个初始化办法initWithPath
和YYMemoryCache
的初始化办法:
- (instancetype)initWithPath:(NSString *)path {
return [self initWithPath:path inlineThreshold:1024 * 20]; // 20KB
}
查看initWithPath:(NSString *)path
inlineThreshold:(NSUInteger)threshold发现:
- (instancetype)initWithPath:(NSString *)path
inlineThreshold:(NSUInteger)threshold {
…
YYKVStorageType type;
if (threshold == 0) {
type = YYKVStorageTypeFile;
} else if (threshold == NSUIntegerMax) {
type = YYKVStorageTypeSQLite;
} else {
type = YYKVStorageTypeMixed;
}
_inlineThreshold = threshold;
…
}
在
YYDiskCache
中能够看到内联阈值是20KB
,_inlineThreshold
被初始化为20KB
。
点进YYKVStorageType
看,发现有三种存储类型:
一、文件
二、数据库
三、自选
typedef NS_ENUM(NSUInteger, YYKVStorageType) {
/// The `value` is stored as a file in file system.
YYKVStorageTypeFile = 0,
/// The `value` is stored in sqlite with blob type.
YYKVStorageTypeSQLite = 1,
/// The `value` is stored in file system or sqlite based on your choice.
YYKVStorageTypeMixed = 2,
};
再看赋值:
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
…
NSData *value = nil;
if (!value) return;
NSString *filename = nil;
if (_kv.type != YYKVStorageTypeSQLite) {
if (value.length > _inlineThreshold) {
filename = [self _filenameForKey:key];
}
}
Lock();
[_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];
Unlock();
}
能够看到value.length
> _inlineThreshold
,filename
会被赋值,点击saveItemWithKey
办法:
- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData {
…
if (filename.length) {
if (![self _fileWriteWithName:filename data:value]) {
return NO;
}
if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) {
[self _fileDeleteWithName:filename];
return NO;
}
return YES;
} else {
if (_type != YYKVStorageTypeSQLite) {
NSString *filename = [self _dbGetFilenameWithKey:key];
if (filename) {
[self _fileDeleteWithName:filename];
}
}
return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];
}
}
能够看到filename.length
假如存在值,就直接写成文件,假如不大于20KB
,运用sqlite
写入。
作者:NSURLCache、FBDiskCache 都是基于 SQLite 数据库的。基于数据库的缓存能够很好的支撑元数据、扩展便利、数据统计速度快,也很容易完成 LRU 或其他筛选算法,唯一不确定的就是数据库读写的功能,为此我评测了一下 SQLite 在真机上的表现。iPhone664G 下,SQLite 写入功能比直接写文件要高,但读取功能取决于数据巨细:当单条数据小于20K 时,数据越小 SQLite 读取功能越高;单条数据大于20K 时,直接写为文件速度会更快一些。
二、YYDiskCache:
(那些相同的参数和办法就不重新写)
特点列表:
/**
假如目标的数据巨细(以字节为单位)大于此值,则目标将
存储为文件,不然目标将存储在sqlite中。
0表明一切目标将存储为分隔的文件,NSUIntegerMax表明一切目标
目标将存储在sqlite中。
默许值为20480 (20KB)。
*/
@property (readonly) NSUInteger inlineThreshold;
/**
假如这个块不是nil,那么这个块将被用来存档目标
NSKeyedArchiver。您能够运用此块来支撑不支撑的目标
恪守' NSCoding '协议。
默许值为空。
*/
@property (nullable, copy) NSData *(^customArchiveBlock)(id object);
/**
假如这个块不是nil,那么这个块将被用来解存档目标
NSKeyedUnarchiver。您能够运用此块来支撑不支撑的目标
恪守' NSCoding '协议。
默许值为空。
*/
@property (nullable, copy) id (^customUnarchiveBlock)(NSData *data);
/**
当需求将目标保存为文件时,将调用此块来生成
指定键的文件名。假如块为空,缓存运用md5(key)作为默许文件名。默许值为空。
*/
@property (nullable, copy) NSString *(^customFileNameBlock)(NSString *key);
#pragma mark - Limit
/**
缓存应包容的最大目标数。
默许值为NSUIntegerMax,即不约束。
这不是一个严厉的约束-假如缓存超越约束,缓存中的一些目标
缓存能够稍后在后台行列中被铲除。
*/
@property NSUInteger countLimit;
/**
在开端铲除目标之前,缓存能够保留的最大总开销。
默许值为NSUIntegerMax,即不约束。
这不是一个严厉的约束-假如缓存超越约束,缓存中的一些目标
缓存能够稍后在后台行列中被铲除。
*/
@property NSUInteger costLimit;
/**
缓存中目标的最大过期时间。
>值为DBL_MAX,即无约束。
这不是一个严厉的约束-假如一个目标超越了约束,目标能够
稍后在后台行列中被驱赶。
*/
@property NSTimeInterval ageLimit;
/**
缓存应保留的最小闲暇磁盘空间(以字节为单位)。
>默许值为0,表明不约束。
假如可用磁盘空间低于此值,缓存将删去目标
开释一些磁盘空间。这不是一个严厉的约束——假如闲暇磁盘空间没有了
超越约束,目标可能稍后在后台行列中被铲除。
*/
@property NSUInteger freeDiskSpaceLimit;
/**
主动修整查看时间距离以秒为单位。默许值是60(1分钟)。
缓存保存一个内部计时器来查看缓存是否达到
它的极限,一旦达到极限,它就开端驱赶物体。
*/
@property NSTimeInterval autoTrimInterval;
/**
设置“YES”为调试启用错误日志。
*/
@property BOOL errorLogsEnabled;
办法列表:
/**
指定的初始化式。
threshold数据存储内联阈值,单位为字节。假如目标的数据
Size(以字节为单位)大于此值,则目标将存储为
文件,不然目标将存储在sqlite中。0表明一切目标都会
NSUIntegerMax表明一切目标都将被存储
sqlite。假如您不知道目标的巨细,20480是一个不错的挑选。
在第一次初始化后,您不应该更改指定途径的这个值。
假如指定途径的缓存实例在内存中已经存在,
该办法将直接回来该实例,而不是创立一个新实例。
*/
- (nullable instancetype)initWithPath:(NSString *)path inlineThreshold:(NSUInteger)threshold NS_DESIGNATED_INITIALIZER;
/**
回来此缓存中的目标数量。
*/
- (NSInteger)totalCount;
/**
以字节为单位的目标开销总数。
*/
- (NSInteger)totalCost;
#pragma mark - 修剪
/**
运用LRU从缓存中移除目标,直到' totalCount '低于指定值。
*/
- (void)trimToCount:(NSUInteger)count;
/**
运用LRU从缓存中移除目标,直到' totalCost '低于指定值,完成后调用回调。
*/
- (void)trimToCost:(NSUInteger)cost;
/**
运用LRU从缓存中删去目标,直到一切过期目标被指定值删去为止。
*/
- (void)trimToAge:(NSTimeInterval)age;
/**
从目标中获取扩展数据。
*/
+ (nullable NSData *)getExtendedDataFromObject:(id)object;
/**
将扩展数据设置为一个目标。
*/
+ (void)setExtendedData:(nullable NSData *)extendedData toObject:(id)object;
@end
三、YYMemoryCache:
特点列表:
/** 存中的目标数量(只读)*/
@property (readonly) NSUInteger totalCount;
/** 缓存中目标的总开销(只读)*/
@property (readonly) NSUInteger totalCost;
/**
主动修整查看时间距离以秒为单位。默许是5.0。
*/
@property NSTimeInterval autoTrimInterval;
/**
假如' YES ',当应用程序收到内存正告时,缓存将删去一切目标。
*/
@property BOOL shouldRemoveAllObjectsOnMemoryWarning;
/**
假如是,当应用程序进入后台时,缓存将删去一切目标。
*/
@property BOOL shouldRemoveAllObjectsWhenEnteringBackground;
/**
当应用程序收到内存正告时要履行的块。
*/
@property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache);
/**
当应用程序进入后台时履行的一个块。
*/
@property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache);
/**
假如' YES ',键值对将在主线程上开释,不然在后台线程上开释。默许为NO。。
*/
@property BOOL releaseOnMainThread;
/**
假如' YES ',键值对将在主线程上开释,不然在后台线程上开释。默许为NO。
*/
@property BOOL releaseAsynchronously;
总结:
比照一下NSCache:
@interface NSCache <KeyType, ObjectType> : NSObject
@property (copy) NSString *name;
@property (nullable, assign) id<NSCacheDelegate> delegate;
- (nullable ObjectType)objectForKey:(KeyType)key;
- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
- (void)removeObjectForKey:(KeyType)key;
- (void)removeAllObjects;
@property NSUInteger totalCostLimit; // limits are imprecise/not strict
@property NSUInteger countLimit; // limits are imprecise/not strict
@property BOOL evictsObjectsWithDiscardedContent;
@end
@protocol NSCacheDelegate <NSObject>
@optional
- (void)cache:(NSCache *)cache willEvictObject:(id)obj;
@end
会发现NSCache
相对简单许多,YYCache
对内存和磁盘缓存给了许多个接口去准备控制缓存的数量和生命周期。