Runtime(IV) - 序列化与反序列化
准备条件
父类 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) - 序列化与反序列化的更多相关文章
- 【原】iOS动态性(四):一行代码实现iOS序列化与反序列化(runtime)
为取得更好的排版效果,本文同样发布在简书上,强烈建议跳转到[1]http://www.jianshu.com/p/fed1dcb1ac9f 一.变量声明 为便于下文讨论,提前创建父类Biology以及 ...
- 一行代码实现iOS序列化与反序列化(runtime)
一.变量声明 为便于下文讨论,提前创建父类Biology以及子类Person: Biology: @interface Biology : NSObject { NSInteger *_hairCou ...
- 实现iOS序列化与反序列化(runtime)
一.变量声明 为便于下文讨论,提前创建父类Biology以及子类Person: Biology: @interface Biology : NSObject { NSInteger *_hairCou ...
- 一行代码实现iOS序列化与反序列化
一.变量声明 为便于下文讨论,提前创建父类Biology以及子类Person: Biology: @interface Biology : NSObject { NSInteger *_hairCou ...
- (记录)Jedis存放对象和读取对象--Java序列化与反序列化
一.理论分析 在学习Redis中的Jedis这一部分的时候,要使用到Protostuff(Protobuf的Java客户端)这一序列化工具.一开始看到序列化这些字眼的时候,感觉到一头雾水.于是,参考了 ...
- Newtonsoft.Json C# Json序列化和反序列化工具的使用、类型方法大全 C# 算法题系列(二) 各位相加、整数反转、回文数、罗马数字转整数 C# 算法题系列(一) 两数之和、无重复字符的最长子串 DateTime Tips c#发送邮件,可发送多个附件 MVC图片上传详解
Newtonsoft.Json C# Json序列化和反序列化工具的使用.类型方法大全 Newtonsoft.Json Newtonsoft.Json 是.Net平台操作Json的工具,他的介绍就 ...
- C#对象序列化与反序列化zz
C#对象序列化与反序列化(转载自:http://www.cnblogs.com/LiZhiW/p/3622365.html) 1. 对象序列化的介绍........................ ...
- [.net 面向对象程序设计进阶] (12) 序列化(Serialization)(四) 快速掌握JSON的序列化和反序列化
[.net 面向对象程序设计进阶] (12) 序列化(Serialization)(四) 快速掌握JSON的序列化和反序列化 本节导读: 介绍JSON的结构,在JS中的使用.重点说明JSON如何在.N ...
- 在C#中,Json的序列化和反序列化的几种方式总结
在这篇文章中,我们将会学到如何使用C#,来序列化对象成为Json格式的数据,以及如何反序列化Json数据到对象. 什么是JSON? JSON (JavaScript Object Notation) ...
随机推荐
- scrapy windows下出现importError:No module named 'win32api'
scrapy windows下出现importError:No module named 'win32api'需安装 pip install pypiwin32
- docker之Dockerfile指令介绍
Docker通过对于在Dockerfile中的一系列指令的顺序解析实现自动的image的构建 通过使用build命令,根据Dockerfiel的描述来构建镜像 通过源代码路径的方式 通过标准输入流的方 ...
- python-面向对象-02_类和对象
类和对象 目标 类和对象的概念 类和对象的关系 类的设计 01. 类和对象的概念 类 和 对象 是 面向对象编程的 两个 核心概念 1.1 类 类 是对一群具有 相同 特征 或者 行为 的事物的一个统 ...
- 接口测试工具-Jmeter使用笔记(三:管理请求服务器信息和Headers参数)
如果使用Jmeter同时执行多个http请求任务,就需要创建多个HTTP取样器,每一个取样器都来手动填写服务器信息和端口号,会非常消耗时间. 解决方法:Jmeter之HTTP请求默认值 1.添加方式 ...
- Jmeter原理
Jmeter结构体系及运行原理 Jmeter结构体系 把Jmeter的结构体系拆分为三维空间,如图: X1~X5:是负载模拟的一个过程,使用这些组件来完成负载的模拟: X1:选择协议,模拟用户请求 ...
- Java中的boxing和unboxing(转)
测试代码: System.out.println(0L == 0);//true System.out.println(((Long)0L).equals(0));//false Integer i1 ...
- [django]前后端分离之JWT用户认证
在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了.于是我 ...
- zhaoyin
1.什么时候用到事务,单个update操作会用到事务吗? 银行转账 /**//*--使用事务--*/ use stuDB go --恢复原来的数据 --update bank set currentM ...
- JS发送短信验证码
<div> <input type="tel" id="mobile" name="mobile" placeholder ...
- apache mod_python 安装
环境:Linux 2.6.32-431.23.3.el6.i686 1.安装python .tgz ./configure --prefix=/usr/local/services/Python- ...