准备条件

父类 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. 洛谷P2303 [SDOi2012] Longge的问题 数论

    看懂了题解,太妙了TT但是想解释的话可能要很多数学公式打起来太麻烦了TT所以我就先只放代码具体推演的过程我先写在纸上然后拍下来做成图片放上来算辣quq 好的那我先滚去做题了做完这题就把题解放上来.因为 ...

  2. 【Python全栈-后端开发】嵩天老师-Django

    嵩天老师-Python云端系统开发入门教程(Django) 视频地址:https://www.bilibili.com/video/av19801429 课前知识储备: 一.课程介绍: 分久必合.合久 ...

  3. swift中 ?和 !的区别

      可选类型(?)与强制解析运算符(!) ?是一种判断后再拆包的语法糖 !是一种强制拆包的语法糖   当你不确定有值的时候就可以用  ? 当你确定有值的时候可以用  !     ?的几种使用场景:1. ...

  4. 坦克大战java版

    吃了可以加血的血块类 import java.awt.*; public class Blood { //血块移动的路径 int[][] pos = { {450,250},{450,252},{45 ...

  5. webpack安装与配置初学者踩坑篇

    webpack是基于nodejs开发出来的前端工具 webpack可以处理js文件的依赖关系,webpack能够处理js的兼容问题,把高级浏览器不识别的语法转换成浏览器正常识别的语法 (jnlp是基于 ...

  6. 虚存管理页面置换算法 — FIFO和RUL算法模拟实现

    本篇博文为追忆以前写过的算法系列第一篇(20081021) 温故知新 目的: 为了解决内存容量有限与多作业执行的冲突.运用了虚拟存储技术.能从逻辑上对内存进行扩充,达到扩充内存的效果.分页存储管理是实 ...

  7. Java面试总结(面试流程及核心面试题)

    Java面试流程及核心面试题 面试整体流程 1.1 简单的自我介绍      我是xxxx,工作xxx年.我先后在xxxx公司.yyyy公司工作.先后做个xxxx项目.yyyy项目. 1.2 你简单介 ...

  8. what's the 回抽

    出自 MBA智库百科(https://wiki.mbalib.com/) 什么是回抽 由于近年来证券投资领域中技术分析的不断普及,许多投资者都认识到了股价(汇价)一旦突破颈线压力,后市看好.但是还有一 ...

  9. 萌新接触前端的第二课——CSS

    前端知识——CSS CSS(英文全称:Cascading Style Sheets) 中文名层叠样式表,是一种用来表现HTML或XML等文件样式的计算机语言.CSS不仅可以静态地修饰网页,还可以配合各 ...

  10. Linux安装go

    在 http://golang.org/dl/下载最新的linux版本,并把它提取到/usr/local目录,在此目录下进行解压缩 $ sudo tar -xzf go1.9.1.linux-amd6 ...