一个简化NSCoding的方案

一个简化NSCoding的方案

距离上一次更新博客已经有近两个月的时间了,最近刚把公司的一个新项目完成。这个方案本身不是特别复杂,如果你熟悉runtime的语法,理解起来会更加容易,不熟悉也不影响,我会在文中对方法进行解释。

ps:先说下最终效果就是,在encode和decode中只需添加一行代码,就能实现NSCoding协议。完整代码在Github上。

文中出现的缩略语:
encode方法:-(void)encodeWithCoder:(NSCoder *)aCoder;
decode方法:- (id)initWithCoder:(NSCoder *)aDecoder;


ok,开始吧,下面一步一步的介绍实现思路

为什么会想到做一个这个解决方案?

NSCoding经常应用在我们将某一个自定义class的结构存储到plist,或者序列化时。如果class中的属性特别多,并且属性的类型还不一样时,写NSCoding的接口就会变得很痛苦。有时候还可能经常对属性进行修改,都要改encode和decode方法,导致更加容易犯错误。有没有一种方案,能让我们随意的增删改class的属性,而不需要去修改encode和decode里的代码呢?


方案

基于上述的问题,了解到runtime中提供了一个方法class_copyPropertyList 通过这个方法可以返回一个类型为objc_property_t 的class的属性列表,这是该方案的理论基础,利用这个函数,我们就能获取任何一个class的属性列表,能不能通过for循环来遍历每一个属性,然后对属性进行encode和decode操作呢?答案显然是可以的,只是还不能直接拿来用。

方案一

起初我做了一个简化版的方案,在该方案中,我假设class中的属性都是Object类型的属性,看下面的代码段:

unsigned int count;
    objc_property_t *properties = class_copyPropertyList([object class], &count);
    for (NSInteger i = 0 ; i < count; i ++) {
        objc_property_t property = properties[i];
        NSString *key = [NSString stringWithUTF8String:property_getName(property)];
        [object setValue:[aDecoder decodeObjectForKey:key] forKey:key];
    }
    free(properties);

该代码段是initWithEncode方法内的。其中object表示被序列化的class。

简单分析一下这个代码的原理:

  1. 首先定义一个objc_property_t 类型 的列表,用来接受 class_copyPropertyList 函数返回的某个类的属性列表。

  2. 在for循环内,通过property_getName 来获取每个属性的名字。

  3. 最后用kvc对每个属性进行decode。

如果你定义的class里面的属性都是Object类型的话,这和方案可以很好解决你的问题。

理解了上面的代码后,我们理解起来encode里面的代码就会简单很多,因为原理还是一样的,获取属性列表,然后对每个属性执行encode操作:

unsigned int count;
    objc_property_t *properties = class_copyPropertyList([object class], &count);
    for (NSInteger i = 0 ; i < count; i ++) {
        objc_property_t property = properties[i];
        NSString *key = [NSString stringWithUTF8String:property_getName(property)];
        [aCoder encodeObject:[object valueForKey:key] forKey:key];
    }
    free(properties);

与decode唯一的区别就是,在for循环内执行的是[aCoder encodeObject:[object valueForKey:key] forKey:key]


至此,一个简单的解决方案完成了。

方案二

如果就止步于此,这个方法就显的普适性太差了,前面我们注意到一个方法 property_getName(property) ,通过这个方法,可以获取一个属性的名字,既然要应对那些基本类型的属性(float,int,long,bool等),我们就必须也要获取属性的类型。这里就要登场出现的第二个函数 property_getAttributes(property) 从函数名我们就能知道,这个函数返回的是一个属性的类型,但是它是以一种什么形态进行返回的呢?我在64bit架构下(注意是64bit下),定义了几种常用的属性类型,一并打印出来:

//属性定义
@property (nonatomic) BOOL boolValue;
@property (nonatomic) NSInteger integValue;
@property (nonatomic) NSUInteger unintegValue;
@property (nonatomic) double doubleValue;
@property (nonatomic) float floatValue;
@property (nonatomic) int32_t int32Value;
@property (nonatomic) int64_t int64Value;
@property (nonatomic ,strong) NSString *stringValue;
@property (nonatomic ,strong) NSArray *arrayValue;
@property (nonatomic ,strong) NSDictionary *dictionValue;
@property (nonatomic ,strong) NSData *dataValue;
@property (nonatomic ,strong) NSNumber *numberValue;
//

........

//打印结果
 2015-09-07 10:46:01.592 property[42792:8428599] key : boolValue  -> attribute : TB,N,V_boolValue
 2015-09-07 10:46:01.593 property[42792:8428599] key : integValue  -> attribute : Tq,N,V_integValue
 2015-09-07 10:46:01.593 property[42792:8428599] key : unintegValue  -> attribute : TQ,N,V_unintegValue
 2015-09-07 10:46:01.593 property[42792:8428599] key : doubleValue  -> attribute : Td,N,V_doubleValue
 2015-09-07 10:46:01.593 property[42792:8428599] key : floatValue  -> attribute : Tf,N,V_floatValue
 2015-09-07 10:46:01.593 property[42792:8428599] key : int32Value  -> attribute : Ti,N,V_int32Value
 2015-09-07 10:46:01.594 property[42792:8428599] key : int64Value  -> attribute : Tq,N,V_int64Value
 2015-09-07 10:46:01.594 property[42792:8428599] key : stringValue  -> attribute : T@"NSString",&,N,V_stringValue
 2015-09-07 10:46:01.594 property[42792:8428599] key : arrayValue  -> attribute : T@"NSArray",&,N,V_arrayValue
 2015-09-07 10:46:01.594 property[42792:8428599] key : dictionValue  -> attribute : T@"NSDictionary",&,N,V_dictionValue
 2015-09-07 10:46:01.594 property[42792:8428599] key : dataValue  -> attribute : T@"NSData",&,N,V_dataValue
 2015-09-07 13:44:47.070 property[49820:8546052] key : numberValue  -> attribute : T@"NSNumber",&,N,V_numberValue

打印结果的顺序是按照我属性定义的顺序打印的,去除不重要的信息我们拿出第一条进行分析,这里有必要说明一下打印的格式:
NSLog("key:%@ -> attribute : %@",key,attribute)
属性的部分仅限于attribute:后面的内容,取出第一条,我们来分析一下
TB,N,V_boolValue
TB是标示不同的类型,从打印结果来看,可以通过TB,Tq,TQ,Tf等来区分属性的类型,所有的Object类型属性都是T@开头的。

前面提到,这是在64bit下运行得到结果,我当时是通过NSInteger在不同bit下所表示不同位数判定,可能32bit和64bit属性的类型名可能会不一样。

下面是32bit下的属性类型:

//32bit打印结果
 2015-09-07 10:56:13.493 property[42981:8435236] key : boolValue  -> attribute : Tc,N,V_boolValue
 2015-09-07 10:56:13.494 property[42981:8435236] key : integValue  -> attribute : Ti,N,V_integValue
 2015-09-07 10:56:13.494 property[42981:8435236] key : unintegValue  -> attribute : TI,N,V_unintegValue
 2015-09-07 10:56:13.495 property[42981:8435236] key : doubleValue  -> attribute : Td,N,V_doubleValue
 2015-09-07 10:56:13.495 property[42981:8435236] key : floatValue  -> attribute : Tf,N,V_floatValue
 2015-09-07 10:56:13.495 property[42981:8435236] key : int32Value  -> attribute : Ti,N,V_int32Value
 2015-09-07 10:56:13.495 property[42981:8435236] key : int64Value  -> attribute : Tq,N,V_int64Value
 2015-09-07 10:56:13.495 property[42981:8435236] key : stringValue  -> attribute : T@"NSString",&,N,V_stringValue
 2015-09-07 10:56:13.495 property[42981:8435236] key : arrayValue  -> attribute : T@"NSArray",&,N,V_arrayValue
 2015-09-07 10:56:13.496 property[42981:8435236] key : dictionValue  -> attribute : T@"NSDictionary",&,N,V_dictionValue
 2015-09-07 10:56:13.496 property[42981:8435236] key : dataValue  -> attribute : T@"NSData",&,N,V_dataValue
 2015-09-07 13:44:47.070 property[49820:8546052] key : numberValue  -> attribute : T@"NSNumber",&,N,V_numberValue

属性顺序跟64bit保持一致。
我们还是把BOOL类型的拉出来看看,发现变成了Tc ,对比结果还发现NSUInteger NSInteger 等,在32bit和64bit下面表示也是不一样的。将所有这些不同的表示进行归类,就得到下面这些代码:

 if ([attribute hasPrefix:@"TB"] || [attribute hasPrefix:@"Tc"]) {        // BOOL
            NSNumber *CXLBool_Number = @([aDecoder decodeBoolForKey:key]);
            [object setValue:CXLBool_Number forKey:key];
        } else if ([attribute hasPrefix:@"Tq"]) {                                //int64
            NSNumber *CXLInt64_Number = @([aDecoder decodeInt64ForKey:key]);
            [object setValue:CXLInt64_Number forKey:key];
        } else if ([attribute hasPrefix:@"Ti"]) {                                //int32
            NSNumber *CXLInt32_Nubmer = @([aDecoder decodeInt32ForKey:key]);
            [object setValue:CXLInt32_Nubmer forKey:key];
        } else if ([attribute hasPrefix:@"TQ"] || [attribute hasPrefix:@"TI"]) { //unInteger
            NSNumber *CXLUnInteger_Number = @([aDecoder decodeInt64ForKey:key]);
            [object setValue:CXLUnInteger_Number forKey:key];
        } else if ([attribute hasPrefix:@"Td"]) {                                //double
            NSNumber *CXLDouble_Number = @([aDecoder decodeDoubleForKey:key]);
            [object setValue:CXLDouble_Number forKey:key];
        } else if ([attribute hasPrefix:@"Tf"]) {                                //float
            NSNumber *CXLFloat_Number = @([aDecoder decodeFloatForKey:key]);
            [object setValue:CXLFloat_Number forKey:key];
        } else if ([attribute hasPrefix:@"T@"]) {                                //object
            [object setValue:[aDecoder decodeObjectForKey:key] forKey:key];
        } else {
            [object setValue:[aDecoder decodeObjectForKey:key] forKey:key];
        }
    }

得到了上面这个if..else..语句。 这里稍微解释一下,为什么基本类型要转换成NSNumber,因为setValue:forKey: 参数需要传id类型的,所以这也是一个变相的解决方法。

到了这里,我们发现,其实代码本身已经有一些复杂度,并且在针对这个问题上,代码量已经不少了。所以如果我们每实现一个class的NSCoding协议,都要写一坨这个,就得不偿失了。
所以我们必须要将这个部分抽象出来,变成一个通用的类方法,以供那些实现NSCoding协议的class来使用,所有的代码在Github上可以找到。封装好之后,实现NSCoding协议,就像下面这么简单:

#import "property.h"
 #import "CXLCodingHelper.h"

@implementation property

- (id)initWithCoder:(NSCoder *)aDecoder {
   self = [super init];
    if (!self) {
       return nil;
   }
   self = [CXLCodingHelper decodeClass:self decoder:aDecoder];
   return self; 
 }


 -(void)encodeWithCoder:(NSCoder *)aCoder {
     [CXLCodingHelper encodeClass:self encoder:aCoder]; 
 }

在encode和decode中都只需要添加一行代码即可。


结束语

方案主体思路到此基本分析结束了,虽然思路本身并不复杂,但是在解决问题的整个过程中,对问题探索的本身是非常有趣的,也许有时候你会被某一个事情突然点燃灵感,这个时候应该做的就是去尝试,去试错,去寻找答案,乐趣就在寻找的过程中。我也是一名iOS初级开发者,行走在探索的路上,共勉!have fun ^ ^

有任何问题欢迎在下面留言,或扫描二维码
wechat

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值