在上一篇博客中,咱们介绍了用plist文件进行数据持久化的方法。虽然简单易用,但随着开发的深刻,你会发现,这种方式仍是有很大的局限性。试想,若是咱们能够将用户的登陆返回信息模型,游戏中角色的属性信息模型进行直接的持久化存取,那是否是很是爽的事,幸运的是,咱们能够经过归档,来设计一个这样的数据模型。git
归档也是iOS提供给开发者的一种数据存储的方式,事实上,几乎全部的数据类型均可以经过归档来进行存取。其存储与读取的过程,主要封装在两个类中:NSKeyedArchiver和NSKeyedUnarchiver。github
归档是将一种或者多种数据类型进行序列化,解归档的过程就是将序列化的数据进行反序列化的解码,这里须要注意一点,归档的核心并不是是数据的持久化处理,而是数据的序列化处理,持久化的处理依然是经过文件存取来实现的。所以,被归档的数据类型都必须遵照一个相同的协议,才能在这个协议的约束下进行正确的归档与解归档,这个协议就是NSCoding协议,咱们能够先来看一下NSCoding中的内容:数组
@protocol NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder; - (id)initWithCoder:(NSCoder *)aDecoder; @end
这个协议很是简单,一个init的归档方法,一个encode的解归档方法,NSCoder就是归档对象。原则上说,不管是什么数据类型的对象,系统的或者是咱们自定义的,均可以经过实现这个协议中的方法来支持归档操做。安全
这种方式,我我的理解,很相似于NSUserDefaults中的standardUserDefaults,只是后者是系统为咱们建立的一个默认plist文件,而rootKey是系统为咱们建立的一个默认的归档键值。提及来比较复杂,举个例子就十分清晰了:框架
NSString *homeDictionary = NSHomeDirectory();//获取根目录 NSString *homePath = [homeDictionary stringByAppendingPathComponent:@"atany.archiver"];//添加储存的文件名 //方式一:经过data数据归档,在将数据写入文件 NSData *data= [NSKeyedArchiver archivedDataWithRootObject:@"123"]; [data writeToFile:homePath atomically:YES]; //方式二:直接写入文件 [NSKeyedArchiver archiveRootObject:@"456" toFile:homePath]; //方式一和方式二的效果彻底同样 只是解归档的时候不一样 //方式一的解归档:先获取data数据,在进行data数据的解归档 NSLog(@"%@",[NSKeyedUnarchiver unarchiveObjectWithData:data]); //方式二的解归档:直接解文件中的归档 NSLog(@"%@",[NSKeyedUnarchiver unarchiveObjectWithFile:homePath]);
上面的示例是对字符串类型进行的归档,是对单一的数据对象进行的归档,固然,这里的对象是支持数组、字典等集合的,但集合其中的对象,也必须所有支持归档操做。优化
除了上面的类方法,咱们还能够本身构造一个归档对象,来对多种不一样的对象进行归档:atom
NSString *homeDictionary = NSHomeDirectory();//获取根目录 NSString *homePath = [homeDictionary stringByAppendingPathComponent:@"atany.archiver"];//添加储存的文件名 //这里建立一个可变的data对象做为归档的容器 NSMutableData * data = [[NSMutableData alloc]init]; //建立一个归档对象,归档后写入data数据 NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data]; //对下面的字符串和int值进行归档序列化 [archiver encodeObject:@"jaki" forKey:@"name"]; [archiver encodeInt:24 forKey:@"age"]; //写入data [archiver finishEncoding]; //写入文件 [data writeToFile:homePath atomically:YES]; //建立解归档的反序列化对象 NSKeyedUnarchiver * unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data]; //进行反序列化 NSString * name = [unarchiver decodeObjectForKey:@"name"]; int age = [unarchiver decodeIntForKey:@"age"]; //打印信息 NSLog(@"\nname:%@\nage:%d",name,age);
结果以下:spa
上面介绍中有提到,原则上,任何遵照了NSCoding协议的类均可以进行归档操做,那么对于咱们自定义的对象,咱们该如何来作呢?设计
首先,咱们新建一个类:指针
仿照上面的例子,咱们写一个这样的类:
@interface MyObject : NSObject @property(nonatomic,strong)NSString * name; @property(nonatomic,assign)int age; @end
对其进行归档:
//进行归档 MyObject * obj = [[MyObject alloc]init]; obj.name = @"jaki"; obj.age = 24; NSData * data = [NSKeyedArchiver archivedDataWithRootObject:obj]; //进行解档 MyObject * obj2 = [NSKeyedUnarchiver unarchiveObjectWithData:data]; NSLog(@"\nname:%@\nage:%d",obj2.name,obj2.age);
直接运行,程序会崩溃掉,打印以下:
能够看出,正是咱们前边说过的,必须遵照归档协议的对象,才能够被归档,咱们在MyObject类中实现以下两个方法:
//解档方法 - (instancetype)initWithCoder:(NSCoder *)coder { if (self=[super init]) { _name = [coder decodeObjectForKey:@"name"]; _age = [coder decodeIntForKey:@"age"]; } return self; } //归档方法 - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:_name forKey:@"name"]; [coder encodeInt:_age forKey:@"age"]; }
添加了上面两个方法,咱们自定义的对象就能够自由归档存取,并能够写入本地,很是cool吧。
经过上面对归档的介绍,咱们能够发现归档一个十分有潜力的应用:能够自由存取自定义的数据对象。这个特性的优点是毫无疑问的,除了可使咱们的数据用起来更加方便,无需屡次解析数据外,安全性也更好。可是也带来了一个缺陷,每一个类都须要实现NSCoding中的两个方法是十分繁琐的,而且类越复杂,这个步骤越繁琐,若是在以后的修改和优化中类作了改变,相应的方法也要作改变,这将增长很大的工做量而且埋下潜在bug的风险。
因此咱们会想,可否设计一个这样的model基类,来使须要存储的model都继承于它,使咱们的model不须要实现NSCoding方法的同时能够支持归档呢,经过runtime和OC语言特性的一些小技巧,咱们是能够作到的。
咱们新建一个BaseModel类,核心方法以下:
//归档与解归档的方法 - (instancetype)initWithCoder:(NSCoder *)coder { self = [super init]; if (self) { //获取全部属性 NSArray * porpertyArray = [self getAllPropertys]; for (NSString * name in porpertyArray) { //去掉属性名前面的_ NSString * key = [name substringFromIndex:1]; //约定好的键值对 c+key [self setValue:[coder decodeObjectForKey:[NSString stringWithFormat:@"c%@",key]] forKey:key]; } } return self; } - (void)encodeWithCoder:(NSCoder *)coder { //获取全部属性 NSArray * porpertyArray = [self getAllPropertys]; for (NSString * name in porpertyArray) { //去掉属性名前面的_ NSString * key = [name substringFromIndex:1]; //约定好的键值对 c+key [coder encodeObject:[self valueForKey:key] forKey:[NSString stringWithFormat:@"c%@",key]]; } } //获取model全部属性 -(NSArray *)getAllPropertys{ NSMutableArray * array = [[NSMutableArray alloc]init]; unsigned int * count = malloc(sizeof(unsigned int)); //调用runtime的方法 //Ivar:方法返回的对象内容对象,这里将返回一个Ivar类型的指针 //class_copyIvarList方法能够捕获到类的全部变量,将变量的数量存在一个unsigned int的指针中 Ivar * mem = class_copyIvarList([self class], count); //进行遍历 for (int i=0; i< *count ; i++) { //经过移动指针进行遍历 Ivar var = * (mem+i); //获取变量的名称 const char * name = ivar_getName(var); NSString * str = [NSString stringWithCString:name encoding:NSUTF8StringEncoding]; [array addObject:str]; } //释放内存 free(count); //注意处理野指针 count=nil; return array; }
经过这样的一个runtime机制,咱们能够很方便的是新建的model继承于这个基类,无需其余处理直接支持归档,修改与优化都不受影响。
这个model集成在了个人一个开源的开发框架中,固然,那里面也综合和许多许多这样方便开发者使用的功能,若是你感兴趣,能够在https://github.com/ZYHshao/YHBaseFoundationTest上面看到。若是你发现了一些bug或者能够添加或者优化的地方,请务必告知我,十分你感谢。QQ:316045346
专一技术,热爱生活,交流技术,也作朋友。
——珲少 QQ群:203317592