• JSON转Model内部实现解析


    一、思路:
    1、通过模型类型获得所有的属性和其类型
    2、对获得的json进行处理。类型处理
    3、考虑字典键值和模型属性名不一致的情况
    4、添加code用于归档
    5、补充JSON转字典、字典转JSON、字典转模型等接口
    6、对处理过的properties做缓存
     
    二、设计模式思考:
    设计模式的选择---------继承、接口、抽象基类的选择。
    在使用方便、高效率、低耦合之间抉择。
     
    三、细节及实现
    先把任务分解,实现各个部分细节,然后进行组合,当然我们要思考好,采用何种设计模式组装。先来看看各个部分的实现细节。
     
    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模型如下

    //属性}
    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;

    输出结果为

    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。
     
    水平有限,错误之处,欢迎大家指正。互相学习。转载请注明出处
  • 相关阅读:
    报错处理
    MySQL8.0跟5.7分组查询表所有字段
    模拟开始时间、结束时间生成历史时间生成曲线模拟数据
    查询电脑登录过的WiFI账号密码
    Samba服务器架设
    CentOS安装GitLab
    申请域名并使用DDNS
    极路由4增强版(极企版)-刷潘多拉固件
    Git命令
    elasticsearch7.6.2 -canal1.1.4集成
  • 原文地址:https://www.cnblogs.com/booksky/p/5083649.html
Copyright © 2020-2023  润新知