iOS - 写一个简单的JSON转Model库

- 0x00 写在前面

JSON转Model对于我们iOS开发来说有多重要就不赘述啦,而在Github上比较出名的有明杰老师的MJExtension,YY大神的YYModel,早些时候有Mantle等等。我也试着写了个简单的JSON转Model库RYModel,欢迎各位朋友点星和PR,以下是实现的过程,各位客官轻喷啊。


- 0x01 定个小目标

我们做这个JSON转Model库虽然简单,但至少要包括以下功能和方法
才能满足简单的日常的开发。

功能 支持
基本类型赋值(bool,int,float...)
Model包含其他Model
Model属性名和JSON中key不同,下文简称这情况叫Mapping
方法 支持
JSON->Model [Class ry_modelWithKeyValue]
JSON String ->Model [Class ry_modelWithKeyValueString]
JSONs -> Models [Class ry_modelsWithKeyValues]
Model -> JSON [Class ry_modelToKeyValue]
Moldes -> JSONs [Class ry_modelsToKeyValues]

- 0x02 分析过程

在分析过程前,建议各位先下载源码RYModel并结合下面的流程图分析

  • ①首先我们得给NSObject添加个类别(这样所有的类都可以使用),然后写一个+ (instancetype)ry_modelWithKeyValue:(NSDictionary *)dic给予外部调用入口。
  • 然后实例化一个类的对象- (instancetype)ry_initWithKeyValue:(NSDictionary *)dic
  • ③得到实例化的对象后,就可以遍历key查找Model是否存在改key- (NSString *)ry_isExistKey:(NSString *)key,此方法中也包含了检测mapping
  • ④得到合法的key后,我们还得判断该key的相对应的类名是否为自定义的类- (BOOL)ry_isSystemClass:(NSString *)key如果是自定义的类(Model包含Model的情况),则递归调用回+ (instancetype)ry_modelWithKeyValue:(NSDictionary *)dic继续解析,直到得到基本类型或者系统类后才用KVC方法赋值,整个解析流程就此结束。
RYModel流程图.png

- 0x03 JSON->Model 部分源码

  • 初始化关键方法:
 + (instancetype)ry_modelWithKeyValue:(NSDictionary *)dic
{
    return [[self alloc] ry_initWithKeyValue:dic];;
}

 - (instancetype)ry_initWithKeyValue:(NSDictionary *)dic
{
    NSAssert([dic isKindOfClass:[NSDictionary class]], @"此数据为非字典,无法解析");
    
    for (NSString *key in [dic allKeys]) {
        NSString *tKey = [self ry_isExistKey:key];
        if(tKey.length != 0){
            // 存在key
            [self ry_setKey:tKey withValue:dic[key]];
        }else{
            // 不存在key
            NSLog(@"不存在该‘%@’字段",key);
        }
    }
    
    return self;
}
  • 判断key是否存在重新Mapping的情况,然后再检查key是否能与Model的属性匹配上,关键方法:
/**
是否存在key,如果有则返回key名(映射名)

@param key 字典key
@return 模型属性名
*/
- (NSString *)ry_isExistKey:(NSString *)key
{
   const char *aKey;
   unsigned int count;
   NSDictionary *mapDic;
   
   // Model属性有映射(mapping)
   if([self respondsToSelector:@selector(ry_modelMapPropertyNames)]){
       mapDic = [self ry_modelMapPropertyNames];
       if(mapDic){
           for (NSString *tKey in mapDic) {
               if([key isEqualToString:mapDic[tKey]]){
                   return tKey;
               }
           }
       }
   }
   
   // Model属性无映射
   aKey = [key UTF8String];
   objc_property_t *propertyList = class_copyPropertyList([self class], &count);
   for (int i = 0 ; i < count; i++) {
       const char *propertyName = property_getName(propertyList[i]);
       if(strcmp(propertyName, aKey) == 0){
           return [NSString stringWithUTF8String:aKey];
       }
   }
   free(propertyList);

   return nil;
}

  • 判断key对应的Model属性是否是系统类,如果是用户自定义的类,则取出相应key的类名,类名继续调用+ (instancetype)ry_modelWithKeyValue:(NSDictionary *)dic,最后得出系统的类为止,再赋值即可,关键方法:
// 赋值
  - (void)ry_setKey:(NSString *)key withValue:(id)value
{
    id aValue;
    
    if([self ry_isSystemClass:key]){
        // 系统类
        aValue = value;
    }else{
        // 自定义类(model嵌套model)
        Class aClass = [self ry_getAttributeClass:key];
        if([value isKindOfClass:[NSArray class]]){
            // 嵌套的model数据是数组
            aValue = [aClass ry_modelsWithKeyValues:value];
        }else{
            // 嵌套的model数据是字典
            aValue = [aClass ry_modelWithKeyValue:value];
        }
    }
    
    [self setValue:aValue forKey:key];
 }
// key是否是系统的类
 - (BOOL)ry_isSystemClass:(NSString *)key
{
    Class aClass = [self ry_getAttributeClass:key];
    
    if(aClass){
        // 判断key的类型是否是系统类
        NSBundle *aBundle = [NSBundle bundleForClass:aClass];
        if(aBundle == [NSBundle mainBundle]){
            // 自定义的类
            return NO;
        }else{
            // 系统类
            return YES;
        }
    }else{
        // 基本类型
        return YES;
    }
}
 /**
 
 获取Model属性的类名
 
 eg: T@"RYCourse",&,N,V_course 获取字符串中的'RYCourse'
 
 @param key Model属性对应的
 @return Model属性的类名
 */
 - (Class)ry_getAttributeClass:(NSString *)key
{
    Class aClass;
    unsigned int count;
    NSRange objRange;
    NSRange dotRange;
    NSString *aClassStr;
    NSMutableString *aAttribute;
    const char *att = "";
    
    objc_property_t *propertyList = class_copyPropertyList([self class], &count);
    for (int i = 0 ; i < count; i++) {
        const char *propertyName = property_getName(propertyList[i]);
        NSString *tStr = [NSString stringWithUTF8String:propertyName];
        if([key isEqualToString:tStr]){
            att = property_getAttributes(propertyList[i]);
            break;
        }
    }
    free(propertyList);
    
    aAttribute  = [[NSMutableString alloc] initWithUTF8String:att];
    objRange = [aAttribute rangeOfString:@"@"];
    if(objRange.location != NSNotFound){
        // key是对象,不是基本类型
        dotRange = [aAttribute rangeOfString:@","];
        aClassStr = [aAttribute substringWithRange:NSMakeRange(3, dotRange.location-1-3)];
        aClass = NSClassFromString(aClassStr);
    }else{
        return nil;
    }
    
    return aClass;
}

- 0x04 最后

至此,JSON->Model就完成了。RYModel有刚刚小目标里提到的方法和功能的源码,有兴趣可以下载看看。如有问题或疑问可以留言或PR。谢谢~


推荐阅读更多精彩内容