您的位置:首页 > 移动开发 > IOS开发

【转】iOS动态性(四):一行代码实现iOS序列化与反序列化(runtime)

2016-06-12 15:05 411 查看
转自:http://www.cnblogs.com/wengzilin/p/5003545.html

为取得更好的排版效果,本文同样发布在简书上,强烈建议跳转到[1]http://www.jianshu.com/p/fed1dcb1ac9f

一、变量声明

为便于下文讨论,提前创建父类Biology以及子类Person:

Biology:

@interface Biology : NSObject

{

NSInteger *_hairCountInBiology;

}

@property (nonatomic, copy) NSString *introInBiology;

@end

@implementation Biology

@end

Person:

import

import “Biology.h”

import

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 = 0; 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);//记得释放掉

objc_property_t是runtime对于属性变量的定义,本质上也是一个结构体(事实上OC是对C的封装,大多数类型的本质都是C结构体)。在runtime.h头文件中只有typedef struct objc_property *objc_property_t,并没有更详细的结构体介绍。虽然runtime的源码是开源的,但这里并不打算深入介绍,这并不影响我们今天的主题。与Ivar的应用同理,获取类的属性变量的代码一般长这样子:

unsigned int outCount, i;

objc_property_t *properties = class_copyPropertyList([self class], &outCount);

for (i = 0; i < outCount; i++) {

objc_property_t property = properties[i];

NSString *propertyName = [[[NSString alloc] initWithCString:property_getName(property)] ;

NSLog(@”property name:%@”, propertyName);

}

free(properties);

3.3 用runtime实现序列化与反序列化

有了前面两节的铺垫,到这里自然就水到渠成了。我们可以在initWithCoder:以及encoderWithCoder:中遍历类的所有变量,取得变量名作为KEY值,最后使用KVC强制取得或者赋值给对象。于是我们可以得到如下的自动序列化与发序列化代码,关键部分有注释:

`

@implementation Person

//解码

(id)initWithCoder:(NSCoder )coder

{

unsigned int iVarCount = 0;

Ivar iVarList = class_copyIvarList([self class], &iVarCount);//取得变量列表,[self class]表示对自身类进行操作

for (int i = 0; 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 = 0;

Ivar ivarList = class_copyIvarList([self class], &varCount);

for (int i = 0; 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);

}
###3.4 优化 上面代码有个缺陷,在获取变量时都是指定当前类,也就是[self class]`。当你的Model对象并不是直接继承自NSObject时容易遗漏掉父类的属性。请牢记3.1节我们提到的:

编解码的范围不能仅仅是自身类的变量,还应当把除NSObject类外的所有层级父类的属性变量也进行编解码!

因此在上面代码的基础上我们我们需要注意一下细节,设一个指针,先指向本身类,处理完指向SuperClass,处理完再指向SuperClass的SuperClass…。代码如下(这里仅以encodeWithCoder:为例,毕竟initWithCoder:同理):

(void)encodeWithCoder:(NSCoder *)coder

{

Class cls = [self class];

while (cls != [NSObject class]) {//对NSObject的变量不做处理

unsigned int iVarCount = 0;

Ivar ivarList = class_copyIvarList([cls class], &iVarCount);/变量列表,含属性以及私有变量*/

for (int i = 0; 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,而只能取父类的属性变量。这时候3.2节部分的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 = 0;

unsigned int propVarCount = 0;

unsigned int sharedVarCount = 0;

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 = 0; 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 = 0;

unsigned int propVarCount = 0;

unsigned int sharedVarCount = 0;

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 = 0; 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);


}

}

3.5 最终的封装

在逻辑上,上面的代码应该是目前为止比较完美的自动序列化与反序列解决方案了。即使某个类的继承深度极其深,变量极其多,序列化的代码也就以上这些。但是我们回到文章第二节提出的几点场景假设,其中有一点提到:

若你的工程中有很多像Person的自定义类需要做序列化操作呢?

如果是在以上场景下,每个Model类都需要写一次上面的代码。这在一定程度上也造成冗余了。同时,你也会觉得这篇文章的标题就是瞎扯淡,根本就不是一行代码的事。上面的代码冗余,我这种对代码有很强洁癖的程序旺是万万接受不了的。那就再封装一层!这里我采用宏的方式(也可以放到某个工具类里面)将上述代码浓缩成一行,放到一个叫WZLSerializeKit.h的头文件中:

define WZLSERIALIZE_CODER_DECODER() \

\

-
95b6
(id)initWithCoder:(NSCoder *)coder \

{ \

NSLog(@”%s”,func); \

Class cls = [self class]; \

while (cls != [NSObject class]) { \

/判断是自身类还是父类/ \

BOOL bIsSelfClass = (cls == [self class]); \

unsigned int iVarCount = 0; \

unsigned int propVarCount = 0; \

unsigned int sharedVarCount = 0; \

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 = 0; 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 = 0; \

unsigned int propVarCount = 0; \

unsigned int sharedVarCount = 0; \

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 = 0; 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); \

} \

}

之后需要序列化的地方只要两步:1、import “WZLSerializeKit.h” 2、调用WZLSERIALIZE_CODER_DECODER();即可。两个字:清爽。

此外,copyWithZone中同样可以用相同的原理对变量进行自动化copy。同样地,我们也可以用一个宏封装掉copyWithZone方法。这里就不再赘述。

值得一提的是,以上代码我已经放到我的Github中,并且提供了CocoaPods支持。使用的时候只需要pod WZLSerializeKit。点 此处 跳转到我的Github.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ios