我们的应用都包含了大量的数据,使用缓存可以让应用程序更快速的响应数据展示、用户输入,使程序高效的运行;
前提
随着项目功能变多变复杂,客户端的存储方式不再统一,功能各不相同;
存储方式
- 最常用的是NSUserDefaults
- 归档的对象需要遵守
NSCoding协议,使用NSKeyedArchiver和NSKeyedUnarchiver来归档和解压 - 数据序列化成
NSData,writeToFile存储至文件 - DataBase,如SQLite
功能
- 数据的时效性
- 数据安全性
- 数据的不同类型,存储形式不同
- 大量数据的效率
统一
我觉得有必要统一存储方式,只开放一个类,这个类能满足所有的功能;
- 数据的生命周期
- 统一缓存空间
- 数据安全存储
- 不同的等级
- 尽可能多的数据类型
- 效率更快,最好是键值对关系
开工
FileStorageObject
typedef NS_ENUM(NSUInteger, MFSFileStorageObjectTimeOutInterval) {
MFSFileStorageObjectIntervalDefault,
MFSFileStorageObjectIntervalTiming, //定时
MFSFileStorageObjectIntervalAllTime //永久
};
@interface MFSFileStorageObject : NSObject <NSCoding>
/** 数据String */
@property (nonatomic, copy, readonly) NSString *storageString;
/** 数据对象 */
@property (nonatomic, strong, readonly) id storageObject;
/** 数据的存储时效性 */
@property (nonatomic, assign, readonly) MFSFileStorageObjectTimeOutInterval storageInterval;
/** 当前对象的标识符(KEY),默认会自动生成,可自定义*/
@property (nonatomic, copy) NSString *objectIdentifier;
/** 存储文件的过期时间
* -1表示永久文件,慎用 */
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
/** 根据(String,URL,Data,Number,Dictionary,Array,Null,实体)初始化对象 */
- (instancetype)initWithObject:(id)object;
@end
FileStorageObject主要作用是存储数据对象,根据-initWithObject:创建FileStorageObject将所有的数据对象进行转换;
支持的类型有NSString、NSURL、NSData、NSNumber、NSDictionary、NSArray、NSNull、自定义实体实体,这些类型其实是做了一层转换,都是转换成String,即storageString;
storageString本质上是一段JSON,进行AES加密,这样的一个好处是传入的自定义对象不再需要实现NSCoding协议,取storageObject对象时再进行AES解析(NSString+MFSEncrypt里设置MFSDefaultAESKey和AES_IV);
storageObject是根据storageString这段JSON生成的对象,每次取值都是重新生成,好处是每次获取的新对象改变不会改变原存储的值,如果需要改变,重新存即可;
storageInterval是存储的数据的失效,包含默认、定时和永久,这边定义的普通可被全局清理,定时的数据自动清理,而永久的不允许被全局清理,但是可被自己的对象根据KEY清理,这个后面说;
objectIdentifier表示对象标识符,无实际作用,只是区分不同的对象,默认根据storageString的md5生成;
FileStorageObject实现了<NSCoding>协议,让这个对象可被序列化存储;
FileStorage
#import "MFSFileStorageObject.h"
typedef NS_ENUM(NSUInteger, MFSFileStorageType) {
MFSFileStorageCache = 0, //Memory
MFSFileStorageArchiver
};
@interface MFSFileStorage : NSObject
@property (nonatomic, strong) NSString *suiteName; //空间
+ (instancetype)defaultStorage;
- (void)setObject:(MFSFileStorageObject *)aObject forKey:(NSString *)aKey type:(MFSFileStorageType)t;
- (MFSFileStorageObject *)objectForKey:(NSString *)aKey;
- (void)removeObjectForKey:(NSString *)aKey;
//删除所有的默认文件,常用方法
- (void)removeDefaultObjectsWithCompletionBlock:(void (^)(long long folderSize))completionBlock;
//删除过期的文件
- (void)removeExpireObjects;
/** 对所有空间做操作 */
/** 删除所有的默认文件,谨慎操作 */
+ (void)removeDefaultObjectsWithCompletionBlock:(void (^)(long long folderSize))completionBlock;
/** 删除过期的文件,谨慎操作 */
+ (void)removeExpireObjects;
@end
FileStorage主要是对FileStorageObject对象操作,因为FileStorageObject实现了<NSCoding>协议,所以此处直接将FileStorageObject对象存储到文件中(为了效率,有兴趣的可以试试其他存储方式);
使用- setObject:forKey:存储FileStorageObject对象,下次再通过-objectForKey:根据key取出FileStorageObject对象;
FileStorageType的作用是存储类型,是载入内存还是硬盘,集合+defaultStorage使用,在内存中存储FileStorageObject对象;
remove方法有3种:
-removeObjectForKey是根据KEY删除数据,可删除永久级别的数据;-removeDefaultObjectsWithCompletionBlock:是删除当前FileStorage对象内的所有默认存储数据,返回数据大小供客户端提示;-removeExpireObjects删除所有过期的数据,异步的;
suiteName有非常重要的作用,区分不同的类别区域,将数据存储到不同的目录中,使用相同的KEY在不同的FileStorage对象中取得不同的数据,同NSUserDefaults里的SuiteName;
+removeDefaultObjectsWithCompletionBlock:和+removeExpireObjects是对整个数据目录进行操作,范围很广;
CacheManager
extern NSString * const MFSCacheManagerObject;
extern NSString * const MFSCacheManagerObjectKey;
extern NSString * const MFSCacheManagerSetObjectNotification; //缓存存数据
extern NSString * const MFSCacheManagerGetObjectNotification; //缓存取数据
extern NSString * const MFSCacheManagerRemoveObjectNotification;//移除缓存
@interface MFSCacheManager : NSObject
/** 默认缓存管理器 */
+ (MFSCacheManager *)defaultManager;
/** nil suite means use the default search list that +defaultManager uses */
- (instancetype)initWithSuiteName:(NSString *)suitename;
/** 根据Key缓存对象,默认duration为0:对象一直存在,清理后失效,object为nil则removeObject
* @param aObject 存储对象,支持String,URL,Data,Number,Dictionary,Array,Null,自定义实体类
* @param aKey 唯一的对应的值,相同的值对覆盖原来的对象 */
- (void)setObject:(id)aObject forKey:(NSString *)aKey;
/** 存储的对象的存在时间,duration默认为0,传-1,表示永久存在,不可被清理,只能手动移除或覆盖移除
* @param duration 存储时间,单位:秒 */
- (void)setObject:(id)aObject forKey:(NSString *)aKey duration:(NSTimeInterval)duration;
/** 存储全局临时对象,toDisk为NO则不占用硬盘空间 */
- (void)setObject:(id)aObject forKey:(NSString *)aKey toDisk:(BOOL)toDisk;
/** 根据Key获取对象(数据相同内存值不同) */
- (id)objectForKey:(NSString *)aKey;
/** 根据Key移除缓存对象 */
- (void)removeObjectForKey:(NSString *)aKey;
/** 异步移除所有duration为0的缓存
folderSize单位是字节,转换M需要folderSize/(1024.0*1024.0) */
- (void)removeObjectsWithCompletionBlock:(void (^)(long long folderSize))completionBlock;
/** 异步检查缓存(duration大于0)的生命,删除过期缓存,建议App启动使用 */
- (void)removeExpireObjects;
/** 不区分空间,对所有数据进行删除,影响甚广,谨慎操作 */
+ (void)removeObjectsWithCompletionBlock:(void (^)(long long folderSize))completionBlock;
/** 不区分空间,对所有缓存进行检查,谨慎操作 */
+ (void)removeExpireObjects;
@end
CacheManager本质是对FileStorage操作;
开放的方法有注释,一看就明白,主要有几点需要注意:
- 开放
+defaultManager供普通数据存储,可被全局清理; - 需要存储不同空间的,
CacheManager使用-initWithSuiteName:初始化; -setObject:forKey:duration:通过duration来控制数据的有效时间,单位是秒,如果传入负数,说明数据永久,只能通过Key使用-removeObjectForKey:移除,-removeObjectsWithCompletionBlock:无法移除;- 使用
-setObject:forKey:toDisk:存储的对象可不进行文件存储,只存在内存中,他和GlobalManager的区别在于是否需要经过FileStorageObject转换,每次获取的都是新对象,修改对原缓存对象无影响;
GlobalManager
@interface MFSGlobalManager : NSObject
/** 根据Key缓存对象,object为nil则removeObject
* @param aObject 存储对象,支持对象类型
* @param aKey 唯一的对应的值,相同的值对覆盖原来的对象 */
+ (void)setObject:(id)aObject forKey:(NSString *)aKey;
/** 根据Key获取对象(数据相同内存值相同) */
+ (id)objectForKey:(NSString *)aKey;
/** 根据Key移除缓存对象 */
+ (void)removeObjectForKey:(NSString *)aKey;
@end
每个客户端都需要保存一些全局数据,一般的做法是创建一个单例对象来保存,GlobalManager就是这个作用,因为单纯的存储在内存中,一处修改,其他地方取值也会变化;
后续
本篇是本人已经设计出的一种Cache方式并具体介绍它已有哪些功能,可能有所欠缺,抛砖引玉;
CacheManager和GlobalManager包含的功能基本上满足了一个App的数据缓存系统,比如用户信息,可根据UserID设置suiteName,每个用户账号都有不同的数据空间,而最好不要对用户信息使用单例存储;
MFSCache源码请访问Github,感谢你提出更好的建议;