另一种归档

//  这个协议方法是用来编码的
//  是针对于该对象中的属性进行编码和解码


- (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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值