一、变量声明
为便于下文讨论,提前创建父类Biology
以及子类Person
:
Biology:
1 2 3 4 5 6 7 8 9 |
@interface Biology : NSObject { NSInteger *_hairCountInBiology; } @property (nonatomic, copy) NSString *introInBiology; @end @implementation Biology @end |
Person:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#import #import "Biology.h" #import @interface Person : Biology { NSString *_father; } @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSInteger age; @end @implementation Person @end |
补充说明
凡是在父类中定义的属性或者变量,末尾都有InBiology标志;反之也成立
二、问题引入
在iOS中一个自定义对象是无法直接存入到文件中的,必须先转化成二进制流才行。从对象到二进制数据的过程我们一般称为对象的序列化(Serialization),也称为归档(Archive)。同理,从二进制数据到对象的过程一般称为反序列化或者反归档。
在序列化实现中不可避免的需要实现NSCoding以及NSCopying(非必须)协议的以下方法:
1 2 3 |
- (id)initWithCoder:(NSCoder *)coder; - (void)encodeWithCoder:(NSCoder *)coder; - (id)copyWithZone:(NSZone *)zone; |
假设我们现在需要对直接继承自NSObject的Person类进行序列化,代码一般长这样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//对变量编码 - (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 |
似乎so easy?至少到目前为止是这样的。但是请考虑以下问题:
- 若Person是个很大的类,有非常多的变量需要进行encode/decode处理呢?
- 若你的工程中有很多像Person的自定义类需要做序列化操作呢?
- 若Person不是直接继承自NSObject而是有多层的父类呢?(请注意,序列化的原则是所有层级的父类的属性变量也要需要序列化);
如果采用开始的传统的序列化方式进行序列化,在碰到以上问题时容易暴露出以下缺陷(仅仅是缺陷,不能称为问题):
- 工程代码中冗余代码很多
- 父类层级复杂容易导致遗漏点一些父类中的属性变量
那是不是有更优雅的方案来回避以上问题呢?那是必须的。这里我们将共同探讨使用runtime来实现一种接口简洁并且十分通用的iOS序列化与反序列方案。
三、runtime: iOS序列化与反序列化利器
3.1 总体思路
观察上面的initWithCoder
代码我们可以发现,序列化与反序列化中最重要的环节是遍历类的变量,保证不能遗漏。
这里需要特别注意的是:
编解码的范围不能仅仅是自身类的变量,还应当把除NSObject类外的所有层级父类的属性变量也进行编解码!
由此可见,这几乎是个纯体力活。而runtime在遍历变量这件事情上能为我们提供什么帮助呢?我们可以通过runtime在运行时获取自身类的所有变量进行编解码;然后对父类进行递归,获取除NSObject外每个层级父类的属性(非私有变量),进行编解码。
3.2 使用runtime获取变量以及属性
runtime中获取某类的所有变量(属性变量以及实例变量)API:
1 |
Ivar *class_copyIvarList(Class cls, unsigned int *outCount) |
获取某类的所有属性变量API:
1 |
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) |
runtime的所有开放API都放在objc/runtime.h
里面。上面的一些数据类型有些同学可能没见过,这里我们先简单地介绍一下,更详细的介绍请自行查阅其他资料,强烈建议打开
Ivar是runtime对于变量的定义,本质是一个结构体:
runtime的所有开放API都放在objc/runtime.h
里面。上面的一些数据类型有些同学可能没见过,这里我们先简单地介绍一下,更详细的介绍请自行查阅其他资料,强烈建议打开
Ivar是runtime对于变量的定义,本质是一个结构体:
1 2 3 4 5 6 7 8 9 |
@interface Biology : NSObject { NSInteger *_hairCountInBiology; } @property (nonatomic, copy) NSString *introInBiology; @end @implementation Biology @end |
Person:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#import #import "Biology.h" #import @interface Person : Biology { NSString *_father; } @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSInteger age; @end @implementation Person @end |
补充说明
凡是在父类中定义的属性或者变量,末尾都有InBiology标志;反之也成立
二、问题引入
在iOS中一个自定义对象是无法直接存入到文件中的,必须先转化成二进制流才行。从对象到二进制数据的过程我们一般称为对象的序列化(Serialization),也称为归档(Archive)。同理,从二进制数据到对象的过程一般称为反序列化或者反归档。
在序列化实现中不可避免的需要实现NSCoding以及NSCopying(非必须)协议的以下方法:
1 2 3 |
- (id)initWithCoder:(NSCoder *)coder; - (void)encodeWithCoder:(NSCoder *)coder; - (id)copyWithZone:(NSZone *)zone; |
假设我们现在需要对直接继承自NSObject的Person类进行序列化,代码一般长这样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//对变量编码 - (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 |
似乎so easy?至少到目前为止是这样的。但是请考虑以下问题:
- 若Person是个很大的类,有非常多的变量需要进行encode/decode处理呢?
- 若你的工程中有很多像Person的自定义类需要做序列化操作呢?
- 若Person不是直接继承自NSObject而是有多层的父类呢?(请注意,序列化的原则是所有层级的父类的属性变量也要需要序列化);
如果采用开始的传统的序列化方式进行序列化,在碰到以上问题时容易暴露出以下缺陷(仅仅是缺陷,不能称为问题):
- 工程代码中冗余代码很多
- 父类层级复杂容易导致遗漏点一些父类中的属性变量
那是不是有更优雅的方案来回避以上问题呢?那是必须的。这里我们将共同探讨使用runtime来实现一种接口简洁并且十分通用的iOS序列化与反序列方案。
三、runtime: iOS序列化与反序列化利器
3.1 总体思路
观察上面的initWithCoder
代码我们可以发现,序列化与反序列化中最重要的环节是遍历类的变量,保证不能遗漏。
这里需要特别注意的是:
编解码的范围不能仅仅是自身类的变量,还应当把除NSObject类外的所有层级父类的属性变量也进行编解码!
由此可见,这几乎是个纯体力活。而runtime在遍历变量这件事情上能为我们提供什么帮助呢?我们可以通过runtime在运行时获取自身类的所有变量进行编解码;然后对父类进行递归,获取除NSObject外每个层级父类的属性(非私有变量),进行编解码。
3.2 使用runtime获取变量以及属性
runtime中获取某类的所有变量(属性变量以及实例变量)API:
1 |
Ivar *class_copyIvarList(Class cls, unsigned int *outCount) |
获取某类的所有属性变量API:
1 |
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) |
runtime的所有开放API都放在objc/runtime.h
里面。上面的一些数据类型有些同学可能没见过,这里我们先简单地介绍一下,更详细的介绍请自行查阅其他资料,强烈建议打开
Ivar是runtime对于变量的定义,本质是一个结构体: