本地数据库搭建(使用归档)
说明:
- 基于归档
- 基于对象(实现 NSCoding 和 NSCopying)
- 基于数组(最外层永远都是数组)
- 对象拥有主键字段(唯一识别),并且主键值为对象(使用 isEqual 来判断相等)
.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
// note : 如果 oldObj 和 addObj 主键重复,又希望可以并存,那么你返回的数组中的元素的主键必须不相同并且也不能和已有的元素的主键相同。
// notice : 如果重复的主键是 123 那么你应该这样修改你的主键 123_repeat_1
// 原因 : 在每次添加数据之前会对待添加的数据做自检(移除待添加数据中主键重复的数据),在添加完之后会对整体数据再做一次自检。如果这时候还存在主键重复 会造成数据遗失
@class CHLocalDBConfiguration;
@interface CHLocalDataBase : NSObject
/**
* 使用 DB 配置对象进行初始化
*
* @param config 自定义配置
*
* @return 自定义 DB
*/
- (id) initWithConfiguration:(CHLocalDBConfiguration *)config;
/**
* 将一类相同类型的对象存储到文件中(增)
*
* @param objs 对象数组
* @param key 主键
* @param path 路径
*/
- (void) localAddObjs:(NSArray<NSCopying , NSCoding> *)objs primaryKey:(NSString *)key toFilePath:(NSString *)path;
/**
* 清空某个文件的内容(删)
*
* @param path 路径
*/
- (void) localClearObjsWithFilePath:(NSString *)path;
/**
* 重置某个文件的数据
*
* @param devics 重新写入的数据
* @param key 主键
* @param path 文件路径
*/
- (void) localResetFile:(NSArray<NSCoding , NSCopying> *)objs primaryKey:(NSString *)key filePath:(NSString *)path;
/**
* 获取到某个文件路径里的所有对象(查)
*
* @param path 路径
*
* @return 所有对象
*/
- (NSArray<NSCoding , NSCopying> *) localAllObjsWithFilePath:(NSString *)path;
/**
* 在指定文件中查找特定的对象(查)
*
* @param path 文件
* @param key 主键
* @param value 主键值数组
*
* @return 对象数组
*/
- (NSArray *) localSearchObjWithFilePath:(NSString *)path primaryKey:(NSString *)key primaryValues:(NSArray *)values;
/**
* 在指定文件中删除特定的对象(删)
*
* @param key 主键
* @param values 主键值数组
* @param path 文件
*/
- (void) localDeleteObjsWithPrimaryKey:(NSString *)key primaryValues:(NSArray *)values filePath:(NSString *)path;
@end
@protocol CHLocalDBConfigurationDelegate <NSObject>
@optional
/**
* 合并重复数据方式
*
* @param oldPbj 已经存储的数据元素
* @param newObj 待存储的重复数据元素
*
* @return 合并后的数据
*/
- (NSArray *) mergeRepeatItemWithOldObj:(NSObject *)oldObj newObj:(NSObject *)newObj;
/**
* 自定义加密
*
* @param data 已经经过 base64 加密的数据
*/
- (NSData *) encryptionData:(NSData *)data;
/**
* 和自定义加密配套的解密
*
* @param data base64 + 自定义加密后的数据
*
* @return base64 加密的数据
*/
- (NSData *) decryptionData:(NSData *)data;
@end
@interface CHLocalDBConfiguration : NSObject
@property (nonatomic , weak) id<CHLocalDBConfigurationDelegate> delegate;
@end
@interface NSArray (ChEx_DB)
/**
* 像一个数组中添加新元素,如果新元素和老元素有重复的,用新元素覆盖老元素
*
* @param newObjs 新数组
* @param key 主键
*/
- (NSArray *) addObjs:(NSArray *)newObjs coverRepeatItemWithPrimaryKey:(NSString *)key;
/**
* 移除一个数组中重复的元素
*
* @param objs 检测数组
* @param key 主键
*/
- (NSArray *) removeRepeatItemsWithPrimaryKey:(NSString *)key;
/**
* 像一个数组中添加新元素,如果新元素和老元素有重复的,使用 block 处理
*
* @param newObjs 新数组
* @param key 主键
* @param block 重复数据处理
*/
- (NSArray *) addObjs:(NSArray *)newObjs mergeRepeatItemWithPrimaryKey:(NSString *)key mergeBlock:(NSArray *(^)(NSObject *oldObj , NSObject *addObj))block;
@end
NS_ASSUME_NONNULL_END
.m
#import "CHLocalDataBase.h"
@interface CHLocalDataBase()
@property (nonatomic , strong) CHLocalDBConfiguration *config;
@end
@implementation CHLocalDataBase
// sync
- (id) initWithConfiguration:(CHLocalDBConfiguration *)config {
self = [super init];
if (self) {
if (config) {
self.config = config;
}
}
return self;
}
// 增
- (void) localAddObjs:(NSArray<NSCopying , NSCoding> *)objs primaryKey:(NSString *)key toFilePath:(NSString *)path {
NSMutableArray *dataArrM = [NSMutableArray new];
NSMutableArray *objArrM = [NSMutableArray new];
// 取出原数据转成数据模型数组
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
for (NSData *data in [NSArray arrayWithContentsOfFile:path]) {
NSData *encryptionData;
// 自定义解密
if (self.config.delegate && [self.config.delegate respondsToSelector:@selector(decryptionData:)]) {
encryptionData = [self.config.delegate decryptionData:data];
}
else {
encryptionData = data;
}
// 解密 base64
NSData * base64Data = [[NSData alloc]initWithBase64EncodedData:encryptionData options:NSDataBase64DecodingIgnoreUnknownCharacters];
NSKeyedUnarchiver *unArchive = [[NSKeyedUnarchiver alloc]initForReadingWithData:base64Data];
id obj = [unArchive decodeObjectForKey:@"data"];
[unArchive finishDecoding];
[objArrM addObject:obj];
}
}
// 添加数据(添加之前会进行自检,去除添加数据中主键重复的多余数据)
NSArray *allObjs = [objArrM addObjs:objs mergeRepeatItemWithPrimaryKey:key mergeBlock:^NSArray * _Nonnull(NSObject * _Nonnull oldObj, NSObject * _Nonnull addObj) {
// 自定义合并准则
if (self.config.delegate && [self.config.delegate respondsToSelector:@selector(mergeRepeatItemWithOldObj:newObj:)]) {
return [self.config.delegate mergeRepeatItemWithOldObj:oldObj newObj:addObj];
}
else {
return @[addObj];
}
}];
// 再次自检
allObjs = [allObjs removeRepeatItemsWithPrimaryKey:key];
// 转 NSData 归档
for (id<NSCopying , NSCoding> obj in allObjs) {
// 序列化
NSMutableData *dataM = [NSMutableData new];
NSKeyedArchiver *archive = [[NSKeyedArchiver alloc]initForWritingWithMutableData:dataM];
[archive encodeObject:obj forKey:@"data"];
[archive finishEncoding];
// 使用 base64 加密
NSData *dataBase64 = [dataM base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];
NSData *encryptionData;
// 自定义加密
if (self.config.delegate && [self.config.delegate respondsToSelector:@selector(decryptionData:)]) {
encryptionData = [self.config.delegate encryptionData:dataBase64];
}
else {
encryptionData = dataBase64;
}
[dataArrM addObject:dataBase64];
}
// 写入数据
BOOL isTure = [dataArrM writeToFile:path atomically:YES];
CHLog(@"本地数据写入:%d",isTure);
}
// 清空
- (void) localClearObjsWithFilePath:(NSString *)path {
NSArray *dataArr = [NSArray new];
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
[dataArr writeToFile:path atomically:YES];
}
}
// 重置
- (void) localResetFile:(NSArray<NSCoding , NSCopying> *)objs primaryKey:(NSString *)key filePath:(NSString *)path {
[self localClearObjsWithFilePath:path];
[self localAddObjs:objs primaryKey:key toFilePath:path];
}
// 查找所有本地存储数据模型数组
- (NSArray<NSCoding , NSCopying> *) localAllObjsWithFilePath:(NSString *)path {
NSMutableArray *arrM = [NSMutableArray new];
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
for (NSData *data in [NSArray arrayWithContentsOfFile:path]) {
NSData *encryptionData;
// 自定义解密
if (self.config.delegate && [self.config.delegate respondsToSelector:@selector(decryptionData:)]) {
encryptionData = [self.config.delegate decryptionData:data];
}
else {
encryptionData = data;
}
// base64
NSData * base64Data = [[NSData alloc]initWithBase64EncodedData:encryptionData options:NSDataBase64DecodingIgnoreUnknownCharacters];
NSKeyedUnarchiver *unArchive = [[NSKeyedUnarchiver alloc]initForReadingWithData:base64Data];
id obj = [unArchive decodeObjectForKey:@"data"];
[unArchive finishDecoding];
[arrM addObject:obj];
}
}
return [arrM copy];
}
// 查找
- (NSArray *) localSearchObjWithFilePath:(NSString *)path primaryKey:(NSString *)key primaryValues:(NSArray *)values {
NSMutableArray *returnObjs = [NSMutableArray new];
NSArray *objs = [self localAllObjsWithFilePath:path];
for (NSObject *value in values) {
for (NSObject *obj in objs) {
if ([[obj valueForKey:key] isEqual:value]) {
[returnObjs addObject:obj];
break;
}
}
}
return [returnObjs copy];
}
// 删除
- (void) localDeleteObjsWithPrimaryKey:(NSString *)key primaryValues:(NSArray *)values filePath:(NSString *)path {
NSMutableArray *objs = [[self localAllObjsWithFilePath:path] mutableCopy];
for (id value in values) {
for (id obj in [objs copy]) {
if ([[(NSObject *)obj valueForKey:key] isEqual:value]) {
[objs removeObject:obj];
break;
}
}
}
[self localResetFile:objs primaryKey:key filePath:path];
}
@end
@implementation CHLocalDBConfiguration
@end
@implementation NSArray (ChEx_DB)
/**
* 像一个数组中添加新元素,如果新元素和老元素有重复的,用新元素覆盖老元素
*
* @param newObjs 新数组
* @param key 主键
*/
- (NSArray *) addObjs:(NSArray *)newObjs coverRepeatItemWithPrimaryKey:(NSString *)key {
return [self addObjs:newObjs mergeRepeatItemWithPrimaryKey:key mergeBlock:^NSArray * _Nonnull(NSObject * _Nonnull oldObj, NSObject * _Nonnull addObj) {
return @[addObj];
}];
}
/**
* 移除一个数组中重复的元素
*
* @param objs 检测数组
* @param key 主键
*/
- (NSArray *) removeRepeatItemsWithPrimaryKey:(NSString *)key {
NSMutableArray *arrM = [NSMutableArray arrayWithArray:[self copy]];
NSMutableArray *arrM2 = [NSMutableArray arrayWithArray:[self copy]];
for (NSObject *obj in [self copy]) {
[arrM2 removeObject:obj];
for (NSObject *obj2 in [arrM2 copy]) {
if ([[obj valueForKey:key] isEqual:[obj2 valueForKey:key]]) {
[arrM2 removeObject:obj2];
[arrM removeObject:obj2];
}
}
}
return [arrM copy];
}
/**
* 像一个数组中添加新元素,如果新元素和老元素有重复的,使用 block 处理
*
* @param newObjs 新数组
* @param key 主键
* @param block 重复数据处理
*/
- (NSArray *) addObjs:(NSArray *)newObjs mergeRepeatItemWithPrimaryKey:(NSString *)key mergeBlock:(NSArray/* 这里使用数组是考虑到了并存的情况*/ *(^)(NSObject *oldObj , NSObject *addObj))block {
NSMutableArray *originalObjs = [NSMutableArray arrayWithArray:self];
// 对添加的数据做一次自检
NSMutableArray *addObjs = [[[NSMutableArray arrayWithArray:newObjs] removeRepeatItemsWithPrimaryKey:key] mutableCopy];
// 找到原数据中和待添加的数据重复的 并删除
for (NSObject *addObj in [addObjs copy]) {
for (NSObject *oObj in [originalObjs copy]) {
if ([[oObj valueForKey:key] isEqual:[addObj valueForKey:key]]) {
[originalObjs removeObject:oObj];
[addObjs removeObject:addObj];
NSArray *mergeObjs;
mergeObjs = block(oObj , addObj);
// 确保同步
while (!mergeObjs) {}
[originalObjs addObjectsFromArray:mergeObjs];
}
}
}
[originalObjs addObjectsFromArray:addObjs];
return originalObjs;
}
@end