准备条件

父类 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. storm配置文件

  2. 20165225《Java程序设计》第三周学习总结

    20165225<Java程序设计>第三周学习总结 1.视频与课本中的学习: 遇到的问题: 问题如下,无法编译. 最后经同学点出要放在同一个打包的文件夹里,于是就运行成功了,下面是4_15 ...

  3. Eclipse实用小插件

    MyBatipse插件 描述 用于mybatis的Dao层或者mapper层的方法直接跳到对应的xml文件对应的方法 安装 进入IDE(eclipse)的Help——>Install New S ...

  4. JQuery is()与hasClass()方法的对比

    is()和hasClass()方法都可以用以检查匹配的所有元素里是否含有指定类名,虽说hasClass(className)函数等价于is(“.className”) 但is()方法比hasClass ...

  5. kmeans笔记

    1.算法过程 a.随机选取k个初始点作为中心点 b.依次计算剩余所有点分别与哪个初始点距离较近,则该点属于哪个簇 c.移动中心点到现在的簇的中心 d.重复b,c两步,直到中心点不再变化算法结束 2.优 ...

  6. 43.1-CAN通讯

    43-CAN通讯 1.CAN 协议简介 CAN是控制器局域网络(Controller Area Network)的简称,它是由研发和生产汽车电子 产品著称的德国 BOSCH 公司开发的,并最终成为国际 ...

  7. c语言指针应用

    指针变量指向数组元素: #import <stdio.h> int main() { int a[10]={1,2,3,4,5,6,7,8,9,0}; int *p; p=a; for ( ...

  8. discuz论坛 模板修改

    门户首页 template/rtj1009_007/portal/index.php 头部二级导航 template/rtj1009_007/common/toubu.php 底部内容文件 templ ...

  9. 矩形嵌套(dp)

    矩形嵌套 时间限制:3000 ms  |  内存限制:65535 KB 难度:4   描述 有n个矩形,每个矩形可以用a,b来描述,表示长和宽.矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a& ...

  10. Ubuntu中Redis的安装与使用

    安装 安装Redis服务器端 apt-get install redis-server 安装完成后,Redis服务器会自动启动,我们检查Redis服务器程序. 检查Redis服务器系统进程 ps -a ...