在iOS中想要把一些对象数据存储到本地需要那个对象实现NSCoding协议下的两个方法
-(void) encodeWithCoder:(NSCoder *)encoder;
-(id) initWithCoder:(NSCoder *)decoder;
然后在这两个方法中实现对象序列化与反序列化的操作.
例如:
Person类有两个属性
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, copy) NSString *passWord;
那么如果需要实现Person类的序列化与反序列化就需要在上述两个方法中分别实现
-(void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:_userName forKey:@"userName"];
[encoder encodeObject:_passWord forKey:@"passWord"];
}
-(id) initWithCoder:(NSCoder *)decoder {
_userName = [decoder decodeObjectForKey:@"userName"];
_passWord = [decoder decodeObjectForKey:@"passWord"];
}
最后在需要对象序列化的地方调用
BOOL isSuccess = [NSKeyedArchiver archiveRootObject:person toFile:@"archiver.dat"];
返回布尔类型可以检查序列化操作是否成功.
然后在需要对象反序列化的地方调用
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile: @"archiver.dat"];
获得已序列化的对象.
当然,序列化的对象可以是实现了Coding协议的对象数组或者其他.
在我的项目中我写了N个需要序列化的对象,然后在N个需要序列化的对象中又有N个属性,然后可能还要不定期的增加、修改、删除某些属性,那我不就需要每天辗转这些序列化对象中做一些很无聊的工作吗?
一想到这个我都能烦死,那该怎么办呢?
我想到了运行时中是有可以动态获取类属性的方法实现的.于是我在某个类中做了以下工作:
//解码
- (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);
}
照以上写法儿一般情况下都是可以满足需求的,因为该实现中获取变量时都是指定当前类,也就是[self class].当序列化对象不是直接继承NSObject时会遗漏掉父类的属性.
所以我们需要对NSObject的上层所有序列对象的类属性进行编解码.但是在用class_copyIvarList获取父类属性的时候程序在[self valueForKey:key]崩掉了.后来了解到获取父类属性用class_copyPropertyList是正常的,如果这样的话在方法中只能做判断是否是当前类了.所以只要把以下代码复制粘贴到 你的序列化对象中就可以不再关注对象的.m文件动态添加、删除、修改类属性了.
另外在其他地方了解到一个问题,那就是
//iOS中打印propertyList会发现有 superClass、description、debugDescription、hash等四个属性。对这几个属性进行encode操作会导致crash。因此在encode前需要屏蔽掉这些key
- (id)initWithCoder:(NSCoder *)coder
{
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];
NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"];
if (varValue && [filters containsObject:key] == NO) {
[self setValue:varValue forKey:key];
}
}
free(ivarList);
free(propList);
cls = class_getSuperclass(cls);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
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];
NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"];
if (varValue && [filters containsObject:key] == NO) {
[coder encodeObject:varValue forKey:key];
}
}
free(ivarList);
free(propList);
cls = class_getSuperclass(cls);
}
}
但是这样的话我那么多序列化对象难道要一个一个粘贴过去么?
能不能再做一层封装?
答案是肯定的
在项目中新建一个.h文件
然后复制下面的代码到.h文件中
#import
#define SERIALIZER_CODER_DECODER() \
\
- (id)initWithCoder:(NSCoder *)coder \
{ \
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]; \
NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"]; \
if (varValue && [filters containsObject:key] == NO) { \
[self setValue:varValue forKey:key]; \
} \
} \
free(ivarList); \
free(propList); \
cls = class_getSuperclass(cls); \
} \
return self; \
} \
\
- (void)encodeWithCoder:(NSCoder *)coder \
{ \
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]; \
NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"]; \
if (varValue && [filters containsObject:key] == NO) { \
[coder encodeObject:varValue forKey:key]; \
} \
} \
free(ivarList); \
free(propList); \
cls = class_getSuperclass(cls); \
} \
}
只需要在你需要序列化的对象中import你的.h文件然后直接调用
SERIALIZER_CODER_DECODER()