iOS 利用运行时将对象序列化与反序列化

在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文件
iOS 利用运行时将对象序列化与反序列化_第1张图片
然后复制下面的代码到.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()

例如
iOS 利用运行时将对象序列化与反序列化_第2张图片
那么现在你运行项目还有什么问题了么?
runtime将序列化变得如此简单.

你可能感兴趣的:(技术笔记)