准备条件

父类 Biology

Biology.h

 #import <Foundation/Foundation.h>

 @interface Biology : NSObject
{
NSInteger *_hairCountInBiology;
} @property (nonatomic, copy) NSString *introInBiology; @end

Biology.m

#import "Biology.h"

@implementation Biology

@end

子类 Person

Person.h

 #import "Biology.h"
#import "Biology.h"
#import <objc/runtime.h> @interface Person : Biology
{
NSString *_father;
} @property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age; @end

Person.m

#import "Person.h"

@implementation Person

@end

在iOS中一个自定义对象是无法直接存入到文件中的,必须先转化成二进制流才行。从对象到二进制数据的过程我们一般称为对象的序列化(Serialization),也称为归档(Archive)。同理,从二进制数据到对象的过程一般称为反序列化或者反归档。

在序列化实现中不可避免的需要实现NSCoding以及NSCopying(非必须)协议的以下方法:

 - (id)initWithCoder:(NSCoder *)coder;
- (void)encodeWithCoder:(NSCoder *)coder;
- (id)copyWithZone:(NSZone *)zone;

对Person类进行序列化,代码是这样:

 //对变量编码
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.name forKey:@"name"];
[coder encodeObject:@(self.age) forKey:@"age"];
[coder encodeObject:_father forKey:@"_father"];
//... ... other instance variables
} //对变量解码
- (id)initWithCoder:(NSCoder *)coder
{
self.name = [coder decodeObjectForKey:@"name"];
self.age = [[coder decodeObjectForKey:@"age"] integerValue];
_father = [coder decodeObjectForKey:@"_father"];
//... ... other instance variables
}

但是请考虑以下问题:

  a. 若Person是个很大的类,有非常多的变量需要进行encode/decode处理呢?

  b. 若你的工程中有很多像Person的自定义类需要做序列化操作呢?

  c. 若Person不是直接继承自NSObject而是有多层的父类呢?(请注意,序列化的原则是所有层级的父类的属性变量也要需要序列化【自己序列化,父类也要序列化,即使父类不需要】);

如果采用开始的传统的序列化方式进行序列化,在碰到以上问题时容易暴露出以下缺陷(仅仅是缺陷,不能称为问题):

  a. 工程代码中冗余代码很多

  b. 父类层级复杂容易导致遗漏点一些父类中的属性变量

那是不是有更优雅的方案来回避以上问题呢?那是必须的。这里我们将共同探讨使用runtime来实现一种接口简洁并且十分通用的iOS序列化与反序列方案。


Runtime 序列化和反序列化

由 initWithCoder 代码我们可以发现,序列化与反序列化中最重要的环节是遍历类的变量,保证不能遗漏。

注意:编解码的范围不能仅仅是自身类的变量,还应当把除NSObject类外的所有层级父类的属性变量也进行编解码!

由此可见,我们会写许多无聊的代码。而runtime在遍历变量这件事情上能为我们提供什么帮助呢?我们可以通过runtime在运行时获取自身类的所有变量进行编解码;然后对父类进行递归,获取除NSObject外每个层级父类的属性(非私有变量),进行编解码。

runtime 获取变量和属性

runtime中获取某类的所有变量(属性变量以及实例变量)API:

 Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

获取某类的所有属性变量API:

 objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

runtime的所有开放API都放在【objc/runtime.h】里面,可以按住 Command 然后鼠标点击 它,进入。

Ivar是runtime对于变量的定义,本质是一个结构体:

 struct objc_ivar {
char *ivar_name;
char *ivar_type;
int ivar_offset;
#ifdef __LP64__
int space;
#endif
} typedef struct objc_ivar *Ivar;
  • ivar_name:变量名,对于一个给定的Ivar,可以通过const char *ivar_getName(Ivar v) 函数获得char *类型的变量名;
  • ivar_type: 变量类型,在runtime中变量类型用字符串表示,用@表示 id 类型,用i表示int类型...。这不在本文讨论之列。类似地,可以通过const char *ivar_getTypeEncoding(Ivar v) 函数获得变量类型;
  • ivar_offset: 基地址偏移字节数,可以不用理会。

获取所有变量的代码

unsigned int numIvars; //成员变量个数
Ivar *vars = class_copyIvarList(NSClassFromString(@"UIView"), &numIvars);
NSString *key=nil;
for(int i = ; i < numIvars; i++) {
Ivar thisIvar = vars[i];
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)]; //获取成员变量的名字
NSLog(@"variable name :%@", key);
key = [NSString stringWithUTF8String:ivar_getTypeEncoding(thisIvar)]; //获取成员变量的数据类型
NSLog(@"variable type :%@", key);
}
free(vars);//记得释放掉

获取类的属性变量的代码

 unsigned int outCount, i;  

 objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (i = ; i < outCount; i++) {
objc_property_t property = properties[i];
NSString *propertyName = [[[NSString alloc] initWithCString:property_getName(property)] ;
NSLog(@"property name:%@", propertyName);
} free(properties);

【objc_property_t】是runtime对于属性变量的定义,本质上也是一个结构体(事实上OC是对C的封装,大多数类型的本质都是C结构体)。在runtime.h头文件中只有【typedef struct objc_property *objc_property_t】,并没有更详细的结构体介绍。


进入正题,用Runtime实现序列化和反序列化

可以在【initWithCoder:】以及【encoderWithCoder:】中遍历类的所有变量,取得变量名作为KEY值,最后使用KVC强制取得或者赋值给对象。

代码下:

 - (id)initWithCoder:(NSCoder* )coder
{
unsigned int iVarCount = ;
Ivar *iVarList = class_copyIvarList([self class], &iVarCount);//取得变量列表,[self class]表示对自身类进行操作
for (int i = ; i < iVarCount; i++) {
Ivar var = *(iVarList + i);
const char varName = *ivar_getName(var);//取得变量名字,将作为key
NSString *key = [NSString stringWithUTF8String:&varName];
//decode
id value = [coder decodeObjectForKey:key];//解码
if (value) {
[self setValue:value forKey:key];//使用KVC强制写入到对象中
}
}
free(iVarList);//记得释放内存
return self;
} //编码
- (void)encodeWithCoder:(NSCoder* )coder
{
unsigned int varCount = ;
Ivar *ivarList = class_copyIvarList([self class], &varCount);
for (int i = ; i < varCount; i++) {
Ivar var = *(ivarList + i);
const char *varName = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:varName];
id varValue = [self valueForKey:key];//使用KVC获取key对应的变量值
if (varValue) {
[coder encodeObject:varValue forKey:key];
}
}
free(ivarList);
}

 上面代码有一个缺陷,在获取变量时都是指定当前类,也就是[self class]。当你的Model对象并不是直接继承自NSObject时容易遗漏掉父类的属性。请注意我们一直提到的:

编解码的范围不能仅仅是自身类的变量,还应当把除NSObject类外的所有层级父类的属性变量也进行编解码!

因此在上面代码的基础上我们我们需要改进一下,设一个指针,先指向本身类,处理完指向SuperClass,处理完再指向SuperClass的SuperClass...。代码如下(这里仅以encodeWithCoder:为例,毕竟initWithCoder:同理):

 //编码
- (void)encodeWithCoder:(NSCoder *)coder
{
Class cls = [self class];
while (cls != [NSObject class]) {//对NSObject的变量不做处理
unsigned int iVarCount = ;
Ivar *ivarList = class_copyIvarList([cls class], &iVarCount);/*变量列表,含属性以及私有变量*/
for (int i = ; i < iVarCount; i++) {
const char *varName = ivar_getName(*(ivarList + i));
NSString *key = [NSString stringWithUTF8String:varName];
/*valueForKey只能获取本类所有变量以及所有层级父类的属性,不包含任何父类的私有变量(会崩溃)*/
id varValue = [self valueForKey:key];
if (varValue) {
[coder encodeObject:varValue forKey:key];
}
}
free(ivarList);
cls = class_getSuperclass(cls); //指针指向当前类的父类
}
}

这样真的结束了吗?不是的。当你运行上面代码时有可能会crash掉,crash的地方在 [self objectForKey:key] 这一句上。原来是这里的KVC无法获取到父类的私有变量(即实例变量)。因此,在处理到父类时不能简单粗暴地使用【class_copyIvarList】,而只能取父类的属性变量。这时的【class_copyPropertyList】就派上用场了。在处理父类时用后者代替前者。于是最终的代码(嗯**其实还不算最终):

 - (id)initWithCoder:(NSCoder *)coder
{
NSLog(@"%s",__func__);
Class cls = [self class];
while (cls != [NSObject class]) {
/*判断是自身类还是父类*/
BOOL bIsSelfClass = (cls == [self class]);
unsigned int iVarCount = ;
unsigned int propVarCount = ;
unsigned int sharedVarCount = ;
Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL;/*变量列表,含属性以及私有变量*/
objc_property_t *propList = bIsSelfClass ? NULL : class_copyPropertyList(cls, &propVarCount);/*属性列表*/
sharedVarCount = bIsSelfClass ? iVarCount : propVarCount; for (int i = ; i < sharedVarCount; i++) {
const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i));
NSString *key = [NSString stringWithUTF8String:varName];
id varValue = [coder decodeObjectForKey:key];
if (varValue) {
[self setValue:varValue forKey:key];
}
}
free(ivarList);
free(propList);
cls = class_getSuperclass(cls);
}
return self;
} - (void)encodeWithCoder:(NSCoder *)coder
{
NSLog(@"%s",__func__);
Class cls = [self class];
while (cls != [NSObject class]) {
/*判断是自身类还是父类*/
BOOL bIsSelfClass = (cls == [self class]);
unsigned int iVarCount = ;
unsigned int propVarCount = ;
unsigned int sharedVarCount = ;
Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL;/*变量列表,含属性以及私有变量*/
objc_property_t *propList = bIsSelfClass ? NULL : class_copyPropertyList(cls, &propVarCount);/*属性列表*/
sharedVarCount = bIsSelfClass ? iVarCount : propVarCount; for (int i = ; i < sharedVarCount; i++) {
const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i));
NSString *key = [NSString stringWithUTF8String:varName];
/*valueForKey只能获取本类所有变量以及所有层级父类的属性,不包含任何父类的私有变量(会崩溃)*/
id varValue = [self valueForKey:key];
if (varValue) {
[coder encodeObject:varValue forKey:key];
}
}
free(ivarList);
free(propList);
cls = class_getSuperclass(cls);
}
}

在 ViewController.m 中分别调用这两个方法:

 - (NSString *)filePath
{
NSString *archiverFilePath = [NSString stringWithFormat:@"%@/archiver", NSHomeDirectory()];
return archiverFilePath;
}

归档:

 - (void) archiveFunction {
Person *person = [[Person alloc] init];
person.name = @"王者荣耀--> 荆轲";
person.age = ;
[person setValue:@"Harely Created me" forKey:@"_father"];
person.introInBiology = @"我最终来自与 Biology";
NSLog(@"Before archiver:\n%@", [person description]); NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:person forKey: @"Person"];
[archiver finishEncoding];
[data writeToFile:[self filePath] atomically:YES];
}

解档:

 - (void) solutionFileFunction {
Person *thePerson = nil;
NSMutableData *dedata = [NSMutableData dataWithContentsOfFile:[self filePath]];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:dedata];
thePerson = [unarchiver decodeObjectForKey:@"Person"];
[unarchiver finishDecoding]; NSLog(@"----->thePerson: name: %@, agee: %ld, introInBiology: %@, _father: %@", thePerson.name, (long)thePerson.age, thePerson.introInBiology, [thePerson valueForKey:@"_father"]);
}

打印结果:

 -- ::58.744385+ RAC[:] ----->thePerson: name: 王者荣耀--> 荆轲, agee: , introInBiology: 我最终来自与 Biology, _father: Harely Created you

一行代码的事

这里我们采用宏的方式将上述代码浓缩成一行,宏定义的 .pch 文件中

具体的代码在:Harely's GitHub

Runtime(IV) - 序列化与反序列化的更多相关文章

  1. 【原】iOS动态性(四):一行代码实现iOS序列化与反序列化(runtime)

    为取得更好的排版效果,本文同样发布在简书上,强烈建议跳转到[1]http://www.jianshu.com/p/fed1dcb1ac9f 一.变量声明 为便于下文讨论,提前创建父类Biology以及 ...

  2. 一行代码实现iOS序列化与反序列化(runtime)

    一.变量声明 为便于下文讨论,提前创建父类Biology以及子类Person: Biology: @interface Biology : NSObject { NSInteger *_hairCou ...

  3. 实现iOS序列化与反序列化(runtime)

    一.变量声明 为便于下文讨论,提前创建父类Biology以及子类Person: Biology: @interface Biology : NSObject { NSInteger *_hairCou ...

  4. 一行代码实现iOS序列化与反序列化

    一.变量声明 为便于下文讨论,提前创建父类Biology以及子类Person: Biology: @interface Biology : NSObject { NSInteger *_hairCou ...

  5. (记录)Jedis存放对象和读取对象--Java序列化与反序列化

    一.理论分析 在学习Redis中的Jedis这一部分的时候,要使用到Protostuff(Protobuf的Java客户端)这一序列化工具.一开始看到序列化这些字眼的时候,感觉到一头雾水.于是,参考了 ...

  6. Newtonsoft.Json C# Json序列化和反序列化工具的使用、类型方法大全 C# 算法题系列(二) 各位相加、整数反转、回文数、罗马数字转整数 C# 算法题系列(一) 两数之和、无重复字符的最长子串 DateTime Tips c#发送邮件,可发送多个附件 MVC图片上传详解

    Newtonsoft.Json C# Json序列化和反序列化工具的使用.类型方法大全   Newtonsoft.Json Newtonsoft.Json 是.Net平台操作Json的工具,他的介绍就 ...

  7. C#对象序列化与反序列化zz

      C#对象序列化与反序列化(转载自:http://www.cnblogs.com/LiZhiW/p/3622365.html) 1. 对象序列化的介绍........................ ...

  8. [.net 面向对象程序设计进阶] (12) 序列化(Serialization)(四) 快速掌握JSON的序列化和反序列化

    [.net 面向对象程序设计进阶] (12) 序列化(Serialization)(四) 快速掌握JSON的序列化和反序列化 本节导读: 介绍JSON的结构,在JS中的使用.重点说明JSON如何在.N ...

  9. 在C#中,Json的序列化和反序列化的几种方式总结

    在这篇文章中,我们将会学到如何使用C#,来序列化对象成为Json格式的数据,以及如何反序列化Json数据到对象. 什么是JSON? JSON (JavaScript Object Notation) ...

随机推荐

  1. vuex的小demo

    效果图: vue的app.vue <template> <div> <p>click {{count}} times,count is {{evenOrOdd}}& ...

  2. python摸爬滚打之day02----while循环,运算符,格式化输出

    1.while循环 1.1  结构:while +条件判断: while 循环体 else: 条件不成立时语句块 while...else...是一个循环整体,当循环条件成立时执行while循环体内容 ...

  3. Feature如何解决参数数量不匹配

    问题描述: Feature 写了两个参数,匹配到Steps.Java,  文件只写了两个参数,但是两个参数都加了$ 符号. 而$ 又是结束的意思. 1一:Feature  用例

  4. js对json格式对象进行增加,修改,删除

    // 假设数据为data var data=[ { "ID":"83d349de-eca4-4974-a8a7-f9b44b48c6f2", "IsL ...

  5. BeanFactoryPostProcessor vs BeanPostProcessor

    BeanFactoryPostProcessors affect BeanDefinition objects because they are run right after your config ...

  6. 【托业】【新托业TOEIC新题型真题】学习笔记1--题库一-->P1~4

    P1~4--------------------------------------单词-------------------------------------- status 雕像 couple ...

  7. vue启动调试、启动编译的批处理

    Rundev.bat cd %~dp0npm run dev RunBuild.bat cd %~dp0npm run build

  8. 基于Kinetic框架实现超酷的风铃悬挂摆动效果

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/iefreer/article/details/37049987 在踏得网开发过程中,我们在引导页面中 ...

  9. 计算机网络 0.初识Internet与TCP/IP协议

    互联网,即因特网,Internet.互联网是一个世界范围的计算机网络.连接了世界上无数的计算设备,这些计算设备为PC.基于Linux的工作站,serverservers等等. 这些设备依据其作用不同可 ...

  10. css中根据不同分辨率设置不同样式

    @media screen and (max-width: ) { .tab__content{width: %} }