// 是针对于该对象中的属性进行编码和解码
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
// 遍历这个类的所有的成员变量
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
const char *varName = ivar_getName(ivars[i]);
NSString *varNameStr = [[NSString alloc ] initWithCString:varName encoding:NSUTF8StringEncoding];
id value = [self valueForKey:varNameStr];
[aCoder encodeObject:value forKey:varNameStr];
}
free(ivars);
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置对应的成员变量
Ivar ivar = ivars[i];
// 查看成员变量
const char *name = ivar_getName(ivar);
// 归档
NSString *key = [[NSString alloc] initWithCString:name encoding:NSUTF8StringEncoding];
id value = [aDecoder decodeObjectForKey:key];// 设置到成员变量身上
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
runtime: iOS序列化与反序列化利器
1 总体思路
观察initWithCoder代码我们可以发现,序列化与反序列化中最重要的环节是遍历类的变量,保证不能遗漏。
这里需要特别注意的是:
编解码的范围不能仅仅是自身类的变量,还应当把除NSObject类外的所有层级父类的属性变量也进行编解码!
由此可见,这几乎是个纯体力活。而runtime在遍历变量这件事情上能为我们提供什么帮助呢?我们可以通过runtime在运行时获取自身类的所有变量进行编解码;然后对父类进行递归,获取除NSObject外每个层级父类的属性(非私有变量),进行编解码。
2 使用runtime获取变量以及属性
runtime中获取某类的所有变量(属性变量以及实例变量)API:
Ivar *class_copyIvarList(Classcls,unsignedint *outCount)
获取某类的所有属性变量API:
objc_property_t *class_copyPropertyList(Classcls,unsignedint *outCount)
runtime的所有开放API都放在objc/runtime.h里面。上面的一些数据类型有些同学可能没见过,这里我们先简单地介绍一下,更详细的介绍请自行查阅其他资料,强烈建议打开
Ivar是runtime对于变量的定义,本质是一个结构体:
structobjc_ivar{
char *ivar_name;
char *ivar_type;
intivar_offset;
#ifdef __LP64__
intspace;
#endif
}
typedefstructobjc_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: 基地址偏移字节数,可以不用理会
获取所有变量的代码一般长这样子:
unsignedintnumIvars;//成员变量个数
Ivar *vars = class_copyIvarList(NSClassFromString(@"UIView"), &numIvars);
NSString *key=nil;
for(inti = 0;i
objc_property_t是runtime对于属性变量的定义,本质上也是一个结构体(事实上OC是对C的封装,大多数类型的本质都是C结构体)。在runtime.h头文件中只有typedef struct objc_property *objc_property_t,并没有更详细的结构体介绍。虽然runtime的源码是开源的,但这里并不打算深入介绍,这并不影响我们今天的主题。与Ivar的应用同理,获取类的属性变量的代码一般长这样子:
unsignedintoutCount,i;
objc_property_t *properties = class_copyPropertyList([selfclass], &outCount);
for(i = 0;i
3. 用runtime实现序列化与反序列化
我们可以在initWithCoder:以及encoderWithCoder:中遍历类的所有变量,取得变量名作为KEY值,最后使用KVC强制取得或者赋值给对象。于是我们可以得到如下的自动序列化与发序列化代码,关键部分有注释:
@implementationPerson
//解码
-(id)initWithCoder:(NSCoder *)coder
{
unsignedintiVarCount = 0;
Ivar *iVarList = class_copyIvarList([selfclass], &iVarCount);//取得变量列表,[self class]表示对自身类进行操作
for(inti = 0;i
4.优化
上面代码有个缺陷,在获取变量时都是指定当前类,也就是[self class]。当你的Model对象并不是直接继承自NSObject时容易遗漏掉父类的属性。:
编解码的范围不能仅仅是自身类的变量,还应当把除NSObject类外的所有层级父类的属性变量也进行编解码!
因此在上面代码的基础上我们我们需要注意一下细节,设一个指针,先指向本身类,处理完指向SuperClass,处理完再指向SuperClass的SuperClass…。代码如下(这里仅以encodeWithCoder:为例,毕竟initWithCoder:同理):
-(void)encodeWithCoder:(NSCoder *)coder
{
Classcls = [selfclass];
while(cls != [NSObjectclass]){//对NSObject的变量不做处理
unsignedintiVarCount = 0;
Ivar *ivarList = class_copyIvarList([clsclass], &iVarCount);/*变量列表,含属性以及私有变量*/
for(inti = 0;i
这样真的结束了吗?不是的。当你的跑上面的代码时程序有可能会crash掉,crash的地方在[self objectForKey:key]这一句上。原来是这里的KVC无法获取到父类的私有变量(即实例变量)。因此,在处理到父类时不能简单粗暴地使用class_copyIvarList,而只能取父类的属性变量。这时候class_copyPropertyList就派上用场了。在处理父类时用后者代替前者。于是:
-(id)initWithCoder:(NSCoder *)coder
{
NSLog(@"%s",__func__);
Classcls = [selfclass];
while(cls != [NSObjectclass]){
/*判断是自身类还是父类*/
BOOLbIsSelfClass = (cls == [selfclass]);
unsignedintiVarCount = 0;
unsignedintpropVarCount = 0;
unsignedintsharedVarCount = 0;
Ivar *ivarList = bIsSelfClass?class_copyIvarList([clsclass], &iVarCount) : NULL;/*变量列表,含属性以及私有变量*/
objc_property_t *propList = bIsSelfClass?NULL : class_copyPropertyList(cls, &propVarCount);/*属性列表*/
sharedVarCount = bIsSelfClass?iVarCount : propVarCount;
for(inti = 0;i