谈谈移动端Cache设计

2015/09/04 iOS

我们的应用都包含了大量的数据,使用缓存可以让应用程序更快速的响应数据展示、用户输入,使程序高效的运行;

前提

随着项目功能变多变复杂,客户端的存储方式不再统一,功能各不相同;

存储方式

  1. 最常用的是NSUserDefaults
  2. 归档的对象需要遵守NSCoding协议,使用NSKeyedArchiverNSKeyedUnarchiver来归档和解压
  3. 数据序列化成NSDatawriteToFile存储至文件
  4. DataBase,如SQLite

功能

  1. 数据的时效性
  2. 数据安全性
  3. 数据的不同类型,存储形式不同
  4. 大量数据的效率

统一

我觉得有必要统一存储方式,只开放一个类,这个类能满足所有的功能;

  1. 数据的生命周期
  2. 统一缓存空间
  3. 数据安全存储
  4. 不同的等级
  5. 尽可能多的数据类型
  6. 效率更快,最好是键值对关系

开工

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里设置MFSDefaultAESKeyAES_IV);

storageObject是根据storageString这段JSON生成的对象,每次取值都是重新生成,好处是每次获取的新对象改变不会改变原存储的值,如果需要改变,重新存即可;

storageInterval是存储的数据的失效,包含默认、定时和永久,这边定义的普通可被全局清理,定时的数据自动清理,而永久的不允许被全局清理,但是可被自己的对象根据KEY清理,这个后面说;

objectIdentifier表示对象标识符,无实际作用,只是区分不同的对象,默认根据storageStringmd5生成;

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操作;

开放的方法有注释,一看就明白,主要有几点需要注意:

  1. 开放+defaultManager供普通数据存储,可被全局清理;
  2. 需要存储不同空间的,CacheManager使用-initWithSuiteName:初始化;
  3. -setObject:forKey:duration:通过duration来控制数据的有效时间,单位是秒,如果传入负数,说明数据永久,只能通过Key使用-removeObjectForKey:移除,-removeObjectsWithCompletionBlock:无法移除;
  4. 使用-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方式并具体介绍它已有哪些功能,可能有所欠缺,抛砖引玉;
CacheManagerGlobalManager包含的功能基本上满足了一个App的数据缓存系统,比如用户信息,可根据UserID设置suiteName,每个用户账号都有不同的数据空间,而最好不要对用户信息使用单例存储;

MFSCache源码请访问Github,感谢你提出更好的建议;

Search

    Post Directory