iOS项目中必用到数据持久化技术,基本原理大致一样,但各自的设计可能不同。这里记录一下我项目中用到的一种实现方式:定义一个全局的单例GlobalData对象,负责根据key-value的形式存取值,对象内部私有化一个字典对象,负责实际的存储工作,最终以NSKeyedArchiver归档的形式持久化该字典。为了能够存储自定义对象,会利用runtime对自定义对象进行自动归解档。
代码参考:
GlobalData.h
#import <Foundation/Foundation.h>
extern NSString *const kUploadLocationSwithKey;//位置上报key
@interface GlobalData : NSObject
/**
保存
@param obj <#obj description#>
@param key <#key description#>
@return <#return value description#>
*/
+ (BOOL)setObject:(id)obj forKey:(NSString *)key;
/**
读取
@param key <#key description#>
@return <#return value description#>
*/
+ (id)objectForKey:(NSString *)key;
/**
清空
@return <#return value description#>
*/
+ (BOOL)clearData;
@end
GlobalData.m
#import "GlobalData.h"
NSString *const kUploadLocationSwithKey = @"kUploadLocationSwithKey";
#define LOCAL_CACHE_PATH [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"GlobalData.archiver"]//添加储存的文件名
@interface GlobalData ()
@property (nonatomic, strong) NSMutableDictionary *dict;
@end
@implementation GlobalData
+ (instancetype)sharedData {
static GlobalData *sharedData = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedData = [[self alloc] init];
});
return sharedData;
}
- (NSDictionary *)dict {
if (!_dict) {
_dict = [NSKeyedUnarchiver unarchiveObjectWithFile:LOCAL_CACHE_PATH];
if (!_dict) {
_dict = [NSMutableDictionary dictionary];
}
}
return _dict;
}
+ (BOOL)setObject:(id)obj forKey:(NSString *)key {
// dispatch_barrier_async(disp, <#^(void)block#>)//同步点
[[GlobalData sharedData].dict setObject:obj forKey:key];
BOOL flag = [NSKeyedArchiver archiveRootObject:[GlobalData sharedData].dict toFile:LOCAL_CACHE_PATH];
return flag;
}
+(id)objectForKey:(NSString *)key {
return [[GlobalData sharedData].dict objectForKey:key];
}
+ (BOOL)clearData {
[[GlobalData sharedData].dict removeAllObjects];
[GlobalData sharedData].dict = nil;
return [[NSFileManager defaultManager] removeItemAtPath:LOCAL_CACHE_PATH error:nil];
}
@end
NSObject+AutoArchive.h
#import <Foundation/Foundation.h>
// 在需要自动归档和解档的模型内内来调用我们的方法,将归解档两个方法封装为宏
#define NSOBJECT_AUTOARCHIVE_METHOD(cls)\
@interface cls ()<NSCoding>\
@end\
@implementation cls\
- (instancetype)initWithCoder:(NSCoder *)aDecoder {\
if (self = [super init]) {\
[self decode:aDecoder];\
}\
return self;\
}\
- (void)encodeWithCoder:(NSCoder *)aCoder {\
[self encode:aCoder];\
}\
@end\
@interface NSObject (AutoArchive)
//不需要归解档的属性数组
- (NSArray *)ignoredPropertyNames;
//Archiver
- (void)encode:(NSCoder *)aCoder;
//Unarchiver
- (void)decode:(NSCoder *)aDecoder;
@end
NSObject+AutoArchive.m
#import "NSObject+AutoArchive.h"
#import <objc/runtime.h>
@implementation NSObject (AutoArchive)
- (NSArray *)ignoredPropertyNames {
return nil;
}
- (void)encode:(NSCoder *)aCoder {
// 一层层父类往上查找,对父类的属性执行归解档方法
Class c = self.class;
while (c &&c != [NSObject class]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; ++i) {
Ivar ivar = ivars[i];
NSString *ivarName = [NSString stringWithCString:ivar_getName(ivar) encoding:NSUTF8StringEncoding];
if ([self respondsToSelector:@selector(ignoredPropertyNames)]) {
if ([[self ignoredPropertyNames] containsObject:ivarName]) {
continue;
}
}
// ivarName = [ivarName substringFromIndex:1];
id value = [self valueForKey:ivarName];
[aCoder encodeObject:value forKey:ivarName];
}
free(ivars);
c = [c superclass];
}
}
- (void)decode:(NSCoder *)aDecoder {
// 一层层父类往上查找,对父类的属性执行归解档方法
Class c = self.class;
while (c &&c != [NSObject class]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; ++i) {
Ivar ivar = ivars[i];
NSString *ivarName = [NSString stringWithCString:ivar_getName(ivar) encoding:NSUTF8StringEncoding];
if ([self respondsToSelector:@selector(ignoredPropertyNames)]) {
if ([[self ignoredPropertyNames] containsObject:ivarName]) {
continue;
}
}
// ivarName = [ivarName substringFromIndex:1];
id value = [aDecoder decodeObjectForKey:ivarName];
[self setValue:value forKey:ivarName];
}
free(ivars);
c = [c superclass];
}
}
@end
NSObject+JCExtension.h
#import <Foundation/Foundation.h>
@interface NSObject (JCExtension)
- (void)setDict:(NSDictionary *)dict;
+ (instancetype)objectWithDict:(NSDictionary *)dict;
+ (NSArray *)objectsWithArray:(NSArray *)arr;
// 返回数组中都是什么类型的模型对象
- (NSString *)objectClassName;
/**
例如接口返回属性是id,oc如果属性名是id会报错,要替换为‘其他的属性名’,这时替换字典就是:@{@"其他的属性名": @"id"}
@return 替换字典
*/
- (NSDictionary *)replacedPropertyName;
@end
NSObject+JCExtension.m
#import "NSObject+JCExtension.h"
#import <objc/runtime.h>
@implementation NSObject (JCExtension)
- (NSString *)objectClassName {
return nil;
}
- (NSDictionary *)replacedPropertyName {
return nil;
}
//第三种情况是模型的属性是一个数组,数组中是一个个模型对象,对象类型未知,调用objectClassName方法获取对象类型,然后递归创建对象类型,加入数组中,最后赋值给外层模型的数组属性
- (void)setDict:(NSDictionary *)dict {
if (!dict || ![dict isKindOfClass:[NSDictionary class]]) return;
Class c = self.class;
while (c &&c != [NSObject class]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(c, &count);
for (int i = 0; i < count; ++i) {
Ivar ivar = ivars[i];
NSString *ivarName = [NSString stringWithCString:ivar_getName(ivar) encoding:NSUTF8StringEncoding];
// 成员变量名转为属性名(去掉下划线 _ )
ivarName = [ivarName substringFromIndex:1];
//要替换的属性:避免有的接口返回id属性
NSDictionary *replace = [self replacedPropertyName];
NSString *replacedName = ivarName;
if (replace) {
if ([replace objectForKey:ivarName]) {
replacedName = [replace objectForKey:ivarName];
}
}
// 取出字典的值
id value = [dict objectForKey:replacedName];
if (!value) {//取值为nil时,直接跳过
continue;
}
// 获得成员变量的类型
NSString *ivarType = [NSString stringWithCString:ivar_getTypeEncoding(ivar) encoding:NSUTF8StringEncoding];
if ([ivarType hasPrefix:@"@"]) { //是对象
ivarType = [ivarType substringWithRange:NSMakeRange(2, ivarType.length-3)]; //@"Person"----->Person
if (![ivarType hasPrefix:@"NS"]) {//去除NS类
Class class = NSClassFromString(ivarType);
value = [class objectWithDict:value];
} else if ([ivarType isEqualToString:@"NSArray"] || [ivarType isEqualToString:@"NSMutableArray"]) {//数组
if ([self respondsToSelector:@selector(objectClassName)]) {
NSString *objectClassName = [self objectClassName];
if (objectClassName) {
Class class = NSClassFromString(objectClassName);
NSMutableArray *arr = [NSMutableArray array];
for (id item in value) {
[arr addObject:[class objectWithDict:item]];
}
value = [arr copy];
}
}
} else if ([ivarType isEqualToString:@"NSString"]) {
if ([value isKindOfClass:[NSNumber class]]) {
value = [NSString stringWithFormat:@"%@", value];
} else if ([value isKindOfClass:[NSNull class]]) {
value = @"";
}
}
}
//kvc赋值
[self setValue:value forKey:ivarName];
}
free(ivars);
c = [c superclass];
}
}
+ (instancetype)objectWithDict:(NSDictionary *)dict {
NSObject *obj = [[self alloc] init];
[obj setDict:dict];
return obj;
}
+ (NSArray *)objectsWithArray:(NSArray *)arr {
if (!arr) {
return nil;
}
NSMutableArray *tmp = [NSMutableArray array];
for (NSDictionary *dict in arr) {
[tmp addObject:[self objectWithDict:dict]];
}
return [tmp copy];
}
@end
CarInfo.h
#import <Foundation/Foundation.h>
#import "NSObject+JCExtension.h"
//字段说明:Id:设备ID号,gpsname:设备别名,islac:定位模式(1基站,2GPS),sta:设备状态,stateFlag:启用状态,gpstime:定位时间,imei:设备唯一编号,lat:矫正后纬度,lon:矫正后经度,Spe:速度,dir:速度,weidu:原始纬度,jindu:原始经度
@interface CarInfo : NSObject
@property (nonatomic, copy) NSString *addre;
/**
<#Description#>
*/
@property (nonatomic, copy) NSString *dir;
@property (nonatomic, copy) NSString *driveID;
@property (nonatomic, copy) NSString *gpsname;
/**
定位时间
*/
@property (nonatomic, copy) NSString *gpstime;
@property (nonatomic, copy) NSString *iconType;
@property (nonatomic, copy) NSString *carId;
@property (nonatomic, copy) NSString *imei;
/**
是否在线
*/
@property (nonatomic, assign) BOOL isOnline;
/**
定位模式(1基站,2GPS)
*/
@property (nonatomic, copy) NSString *islac;
@property (nonatomic, copy) NSString *jindu;
/**
上次定位时间
*/
@property (nonatomic, copy) NSString *lasttime;
@property (nonatomic, copy) NSString *lat;
@property (nonatomic, copy) NSString *lon;
@property (nonatomic, copy) NSString *pic;
@property (nonatomic, copy) NSString *sign;
@property (nonatomic, copy) NSString *sortID;
/**
速度
*/
@property (nonatomic, copy) NSString *spe;
/**
设备状态
*/
@property (nonatomic, copy) NSString *sta;
/**
启用状态
*/
@property (nonatomic, assign) BOOL stateFlag;
@property (nonatomic, copy) NSString *strUid;
@property (nonatomic, copy) NSString *tel;
@property (nonatomic, copy) NSString *vol;
@property (nonatomic, copy) NSString *weidu;
@property (nonatomic, copy) NSString *wlID;
@end
CarInfo.m
#import "CarInfo.h"
#import "NSObject+AutoArchive.h"
@implementation CarInfo
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
[self decode:aDecoder];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[self encode:aCoder];
}
- (NSDictionary *)replacedPropertyName {
return @{@"carId": @"id"};
}
@end