项目中用到的一种数据持久化方式

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