JSON转Model内部实现解析

1、思路:
一、经过模型类型得到全部的属性和其类型
二、对得到的json进行处理。类型处理
三、考虑字典键值和模型属性名不一致的状况
四、添加code用于归档
五、补充JSON转字典、字典转JSON、字典转模型等接口
六、对处理过的properties作缓存
 
2、设计模式思考:
设计模式的选择---------继承、接口、抽象基类的选择。
在使用方便、高效率、低耦合之间抉择。
 
3、细节及实现
先把任务分解,实现各个部分细节,而后进行组合,固然咱们要思考好,采用何种设计模式组装。先来看看各个部分的实现细节。
 
1.经过模型类型得到全部的属性和其类型,
   
 unsigned int outCount = 0;
     //得到Class c全部属性这里的c是[Model class]
    objc_property_t *properties = class_copyPropertyList(c, &outCount);
    
    for (int i = 0; i < outCount; i++) {
        objc_property_t propert = properties[i];
       //得到属性名
        NSString *key = @(property_getName(propert));
        //得到属性类型,如CGFloat、nonatomic、copy等信息
        NSString *type = @(property_getAttributes(propert));
        NSLog(@"key = %@ , type = %@", key, type);
     }

 Model模型以下json

//属性}
typedef void(^block)();
@interface Model : NSObject
@property (nonatomic, copy) NSString *q_NSString;
@property (nonatomic, assign) CGFloat q_CGFloat;
@property (nonatomic, assign) CGRect q_CGRect;
@property (nonatomic, assign) double q_double;
@property (nonatomic, assign) int q_int;
@property (nonatomic, assign) BOOL q_bool;
@property (nonatomic, assign) float q_float;
@property (nonatomic, assign) short q_short;
@property (nonatomic, assign) long q_long;
@property (nonatomic, assign) long long q_longlong;
@property (nonatomic, assign) Point q_point;

@property (nonatomic, strong) id q_id;
@property (nonatomic, weak) id<NSObject> q_delegate;
@property (nonatomic, copy) block q_block;
@property (nonatomic, strong) Model1 *q_model1;
 
@property SEL q_SEL;
@property Class q_Class;
@property Ivar q_Ivar;
@property Method q_Method;

输出结果为swift

key = q_NSString , type = T@"NSString",C,N,V_q_NSString
key = q_CGFloat , type = Td,N,V_q_CGFloat
key = q_CGRect , type = T{CGRect={CGPoint=dd}{CGSize=dd}},N,V_q_CGRect
key = q_double , type = Td,N,V_q_double
key = q_int , type = Ti,N,V_q_int
key = q_bool , type = TB,N,V_q_bool
key = q_float , type = Tf,N,V_q_float
key = q_short , type = Ts,N,V_q_short
key = q_long , type = Tq,N,V_q_long
key = q_longlong , type = Tq,N,V_q_longlong
key = q_point , type = T{Point=ss},N,V_q_point
key = q_id , type = T@,&,N,V_q_id
key = q_delegate , type = T@"<NSObject>",W,N,V_q_delegate
key = q_block , type = T@?,C,N,V_q_block
key = q_model1 , type = T@"Model1",&,N,V_q_model1
key = q_SEL , type = T:,V_q_SEL
key = q_Class , type = T#,&,V_q_Class
key = q_Ivar , type = T^{objc_ivar=},V_q_Ivar
key = q_Method , type = T^{objc_method=},V_q_Method

 

将type用”,”分开, T@"NSNumber",N,R,Vname  为例
在类中的声明为 let name: NSNumber =  NSNumber()
  • T@“NSNumber” 标记了属于什么类型
  • N            线程安全 至关与Objective-C中的nonmatic
  • R            不可变,R至关与Objective-C中的readonly,C至关于copy        
  • Vname        去掉V,name就是变量名
经过对type进行处理就能够得到属性的类型。从而进行下一步处理。
注意点:class_copyPropertyList返回的仅仅是对象类的属性,class_copyIvarList返回类的全部属性和变量,在swift中如let a: Int? 是没法经过class_copyPropertyList返回的。
 
2.对type的处理使用方法可能不一样,可是目的是同样的,就是为了从type字符串中区分出不一样的类型
主要分为这几种:对象、协议、block、基本类型、结构体、自定义类型、Class、Ivar、Method、SEL、
 
MJExtension采用这样区分,在MJExtensionConst中这样定义
NSString *const MJPropertyTypeInt = @"i";
NSString *const MJPropertyTypeShort = @"s";
NSString *const MJPropertyTypeFloat = @"f";
NSString *const MJPropertyTypeDouble = @"d";
NSString *const MJPropertyTypeLong = @"l";
NSString *const MJPropertyTypeLongLong = @"q";
NSString *const MJPropertyTypeChar = @"c";
NSString *const MJPropertyTypeBOOL1 = @"c";
NSString *const MJPropertyTypeBOOL2 = @"b";
NSString *const MJPropertyTypePointer = @"*";

NSString *const MJPropertyTypeIvar = @"^{objc_ivar=}";
NSString *const MJPropertyTypeMethod = @"^{objc_method=}";
NSString *const MJPropertyTypeBlock = @"@?";
NSString *const MJPropertyTypeClass = @"#";
NSString *const MJPropertyTypeSEL = @":";
NSString *const MJPropertyTypeId = @"@";
MJExtension采用对type进行字符串处理就可以区分出具体的类型
而在JSONModel采用 NSScanner,对类型进行处理。
比较下,我的以为采用定义MJExtensionConst这样一个类来存放区分的值显得更加清晰。对于阅读源代码的人来讲相对比较好。固然也能够结合起来使用
 
3.对得到的JSON进行类型处理。

  • 在JSON中为NSNumer,而propertytype为NSString,这种状况很常见。咱们就须要处理一下,当propertytype为NSString,而在JSON中为NSNumber,就把NSNumber转化为NSString。
  • Readonly不须要赋值
  • nil处理
  • 可变和不可变处理
  • 模型就须要递归处理
  • NSString -> NSURL
  • 字符串转BOOL
  • 还有一些其余处理,以上的处理中也不是每一个第三方都进行处理了
截取MJEextension中的一部分代码
        
  if ([value isKindOfClass:[NSStringclass]]) {
                    if (propertyClass == [NSURL class]) {
                        // NSString -> NSURL
                        // 字符串转码
                        value = [value mj_url];
                    } else if (type.isNumberType) {
                        NSString *oldValue = value;
                       
                        // NSString -> NSNumber
                        value = [numberFormatter_ numberFromString:oldValue];
                       
                        // 若是是BOOL
                        if (type.isBoolType) {
                            // 字符串转BOOL(字符串没有charValue方法)
                            // 系统会调用字符串的charValue转为BOOL类型
                            NSString *lower = [oldValue lowercaseString];
                            if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) {
                                value = @YES;
                            } else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) {
                                value = @NO;
                            }
                        }
                    }
                }

 

4.采用KVC赋值
setValue:forKey:
 就很少说了
 
5.考虑字典键值和模型属性名不一致的状况
好比id、descripition不能做为属性名,当服务器使用时咱们就须要另外构造名字;还有服务器通常使用user_name的命名方式,而OC中通常使用驼峰命名法即userName,那么咱们就须要创建一对一的对应关系。
 
我想到有三种方法处理:
  •      采用继承,定义model基类,子类重写父类方法。
  •      block设置block回调。
  •      能够采用抽象基类。
      这个部分就须要咱们好好考虑下了,由于涉及到咱们如何提供接口,使用者如何使用的问题。须要考虑到使用是否方便、效率高低、低耦合是否高等问题。
     先来讲第一种,采用继承的方法,无疑的会带来高耦合的后果,固然在设计合理的状况下,大部分状况下使用起来更方便。
     第二种采用block的方法。解决了继承带来的高耦合的成本。使用起来也很方便。MJEextension就是用这种方法。可是这种方法每每让咱们在调用转化方法时调用block,些一大串的不一样键值的转化。我的以为不一样键值的转化写在model里面比较好,并且在封装时,就有意识地引导使用者这么作
  第三种方法使用抽象基类,定义一个协议,协议提供一个或者几个键值转化的方法。当model遵照并实现了此协议,就找处处理方法进行处理。这种做法避免了继承的高耦合的成本,也是使用者在model遵照协议,实现此方法。键值转化的方法写在model里面,避免转化的地方些太多东西。有时候还要写几遍。固然这样也有些缺点,就是每次有须要转化时(并且键值不一样的状况很常见)须要引入,而后遵照协议。连续建几个model时,复制起来都不方便。
 
6.其余方面
    (未测试) 对于SEL、Method 、Ivar不能使用KVC,若是考虑这些状况,须要进行判断。
    在赋值时,设置能够忽略的属性
 
7.添加code用于归档
  添加方法
- (id)initWithCoder:(NSCoder *)decoder
- (void)encodeWithCoder:(NSCoder *)encoder
 
通常是给NSObject添加分类,由于咱们能够获得类的全部属性和对应的值。在两个方法中遍历属性,进行编码和解码。
方便作些缓存,不少的页面缓存就能够这样作。使用比较方便。
 
8.补充JSON转字典
+ (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString {
  if (jsonString == nil) {
    return nil;
  }
  NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
  NSError *error;
  NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData        
                                                     options:NSJSONReadingMutableContainers
                                                       error:&error];
  if(error) {
    NSLog(@"json解析失败:%@",error);
    return nil;
  }
  return dic;
}
 
此方法稍做变换,也可输出数组,主要看json格式。
 
9.字典转JSON
+ (NSString*)dictionaryToJson:(NSDictionary *)dic
{
    NSError *error = nil;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic 
                                 options:NSJSONWritingPrettyPrinted
                                 error:&error]; return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; }

 

10.对处理过的properties通常须要作缓存
     定义一个字典缓存,缓存处理过的properties,重复使用是不须要重复计算,用空间换时间。例如在归档的时候须要得到属性名列表,而后遍历归档、解挡。其余中间操做也须要用到properties。
 
水平有限,错误之处,欢迎你们指正。互相学习。转载请注明出处
相关文章
相关标签/搜索