【iOS】JSONModel源码阅读笔记

打印 上一主题 下一主题

主题 776|帖子 776|积分 2328


媒介

JSONModel是一个很经典的源码库,先前基本把iOS的底层看的差不多了,暑假之前都会举行源码与算法学习
一、JSONModel使用

之前已经详细写过使用,有一些没写到的使用会在后面增补
【iOS】JSONModel的基本使用
二、JSONModel其他方法

转换属性名称

有时候我们的JSONModel的属性名称与传入的字典的键值不对应,我们就必要使用keyMapper来转换JSON键,将模型中的属性转换为KeyMapper中对应的键名,dictionary中的key值为属性名,value为json键名
  1. #import <JSONModel/JSONModel.h>
  2. @interface UserProfile : JSONModel
  3. @property (strong, nonatomic) NSString* userId;
  4. @property (strong, nonatomic) NSString* emailAddress;
  5. @property (strong, nonatomic) NSString* firstName;
  6. @property (strong, nonatomic) NSString* lastName;
  7. @end
复制代码
设置键映射
为了将JSON键映射到正确的模型属性,你必要在模型类中重写+ (JSONKeyMapper *)keyMapper方法,指定怎样将模型中的属性转换为JSON中的键名
  1. @implementation UserProfile
  2. // 使用 JSONKeyMapper 来定义 JSON 字段与模型属性之间的映射关系
  3. + (JSONKeyMapper *)keyMapper {
  4.     return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
  5.         @"userId": @"user_id",
  6.         @"emailAddress": @"email_address",
  7.         @"firstName": @"profile_info.first_name",
  8.         @"lastName": @"profile_info.last_name"
  9.     }];
  10. }
  11. @end
复制代码
三、源码分析

先看流程图

看一下源代码的目录,可以看到以下内容

在JSONModel中提供了四种初始化方法
  1. -(instancetype)initWithString:(NSString*)string error:(JSONModelError**)err;
  2. -(instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError**)err;
  3. -(instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err;
  4. -(instancetype)initWithData:(NSData *)data error:(NSError **)error;
复制代码
四种方法大同小异,我们从最经典的-(instancetype)initWithDictionaryNSDictionary*)dict errorNSError **)err;讲起
- (instancetype)initWithDictionaryNSDictionary*)dict errorNSError **)err

照旧先查看源代码
  1. -(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
  2. {
  3.     //方法1. 参数为nil
  4.     if (!dict) {
  5.         if (err) *err = [JSONModelError errorInputIsNil];
  6.         return nil;
  7.     }
  8.     //方法2. 参数不是nil,但也不是字典
  9.     if (![dict isKindOfClass:[NSDictionary class]]) {
  10.         if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];
  11.         return nil;
  12.     }
  13.     //方法3. 初始化
  14.     self = [self init];
  15.     if (!self) {
  16.         //初始化失败
  17.         if (err) *err = [JSONModelError errorModelIsInvalid];
  18.         return nil;
  19.     }
  20.     //方法4. 检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO)
  21.     if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
  22.         return nil;
  23.     }
  24.     //方法5. 核心方法:字典的key与模型的属性的映射
  25.     if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
  26.         return nil;
  27.     }
  28.     //方法6. 可以重写[self validate:err]方法并返回NO,让用户自定义错误并阻拦model的返回
  29.     if (![self validate:err]) {
  30.         return nil;
  31.     }
  32.     //方法7. 终于通过了!成功返回model
  33.     return self;
  34. }
复制代码
可以看到源代码分为了七步,我们来渐渐解说


  • 第一步:首先举行判错操纵,查询参数是否为空,如果为空则直接返回nil
  • 第二步:查看参数dict是否为字范例例,如果不是则也返回nil
  • 第三步:初始化JSONModel模型,设置Model的属性集合
  • 第四步:查询是否存在KeyMapper,如果有则举行底层dict键名的变换,然后验证Model中的属性是否都在dict的键名匹配,如果有没有被匹配的则会报错
  • 第五步:焦点方法,终于到了dict的键值与模型的Model的相互映射
  • 第六步:可以重写[self validate:err]方法并返回NO,让用户自界说错误并阻拦model的返回
  • 第七步:返回JSONModel
整个过程看起来并不难,但是涉及到的底层知识点比较多
重点主要在第三步第四步第五步,我们来看一下
但是在解说之前,我们必须讲一下JSONModel中持有的一些关联对象的数据



  • 关联对象kClassPropertiesKey用来保存全部属性信息的NSDictionary)
  • 关联对象kClassRequiredPropertyNamesKey:(用来保存全部属性的名称NSSet)
  • 关联对象kMapperObjectKey:(用来保存JSONKeyMapper):自界说的mapper,具体的使用方法在上面的例子中可以看到。
[self init]

先来看一下第三步的源代码
  1. self = [self init];
  2.     if (!self) {
  3.         //super init didn't succeed
  4.         if (err) *err = [JSONModelError errorModelIsInvalid];
  5.         return nil;
  6.     }
复制代码
[self init]的代码调用了init初始化函数,实现如下,我们主要关注的是其中[self setup]
  1. - (id)init
  2. {
  3.     self = [super init];
  4.     if (self) {
  5.         //do initial class setup
  6.         [self __setup__];
  7.     }
  8.     return self;
  9. }
复制代码
setup

来看一下setup的实现
  1. - (void)__setup__
  2. {
  3.     //if first instance of this model, generate the property list
  4.     // 如果是该模型的第一个实例,则生成属性列表
  5.     if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {
  6.         [self __inspectProperties];
  7.     }
  8.     //if there's a custom key mapper, store it in the associated object
  9.     id mapper = [[self class] keyMapper];
  10.     if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
  11.         objc_setAssociatedObject(
  12.                                  self.class,
  13.                                  &kMapperObjectKey,
  14.                                  mapper,
  15.                                  OBJC_ASSOCIATION_RETAIN // This is atomic
  16.                                  );
  17.     }
  18. }
复制代码
我们来渐渐分析
第一句代码(!objc_getAssociatedObject(self.class, &kClassPropertiesKey))查看当前类是否存在关联对象,如果是该模型的第一个实例,则生成属性列表,也就是调用[self __inspectProperties];检索属性
检索完之后如果存在keyMapper将keyMapper也与模型类举行关联
  1. if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
  2.         objc_setAssociatedObject(
  3.                                  self.class,
  4.                                  &kMapperObjectKey,
  5.                                  mapper,
  6.                                  OBJC_ASSOCIATION_RETAIN // This is atomic
  7.                                  );
  8.     }
复制代码
__inspectProperties

  1. -(void)__inspectProperties
  2. {
  3. //    最终保存所有属性的字典,形式为:
  4. //    {
  5. //        age = "@property primitive age (Setters = [])";
  6. //        friends = "@property NSArray* friends (Standard JSON type, Setters = [])";
  7. //        gender = "@property NSString* gender (Standard JSON type, Setters = [])";
  8. //        name = "@property NSString* name (Standard JSON type, Setters = [])";
  9. //    }
  10.     NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary];
  11.     //获取当前的类名
  12.     Class class = [self class];   
  13.     NSScanner* scanner = nil;
  14.     NSString* propertyType = nil;
  15.     // 循环条件:当class 是 JSONModel自己的时候终止
  16.     while (class != [JSONModel class]) {        
  17.         //属性的个数
  18.         unsigned int propertyCount;
  19.         //获得属性列表(所有@property声明的属性)
  20.         objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
  21.         //遍历所有的属性
  22.         for (unsigned int i = 0; i < propertyCount; i++) {
  23.             //获得属性名称
  24.             objc_property_t property = properties[i];//获得当前的属性
  25.             const char *propertyName = property_getName(property);//name(C字符串)            
  26.             //JSONModel里的每一个属性,都被封装成一个JSONModelClassProperty对象
  27.             JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
  28.             p.name = @(propertyName);//propertyName:属性名称,例如:name,age,gender
  29.             //获得属性类型
  30.             const char *attrs = property_getAttributes(property);
  31.             NSString* propertyAttributes = @(attrs);
  32.             // T@"NSString",C,N,V_name
  33.             // Tq,N,V_age
  34.             // T@"NSString",C,N,V_gender
  35.             // T@"NSArray",&,N,V_friends            
  36.             NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];
  37.             //说明是只读属性,不做任何操作
  38.             if ([attributeItems containsObject:@"R"]) {
  39.                 continue; //to next property
  40.             }
  41.             //检查出是布尔值
  42.             if ([propertyAttributes hasPrefix:@"Tc,"]) {
  43.                 p.structName = @"BOOL";//使其变为结构体
  44.             }            
  45.             //实例化一个scanner
  46.             scanner = [NSScanner scannerWithString: propertyAttributes];
  47.             [scanner scanUpToString:@"T" intoString: nil];
  48.             [scanner scanString:@"T" intoString:nil];
  49.             //http://blog.csdn.net/kmyhy/article/details/8258858           
  50.             if ([scanner scanString:@"@"" intoString: &propertyType]) {               
  51.                  //属性是一个对象
  52.                 [scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@""<"]
  53.                                         intoString:&propertyType];//propertyType -> NSString               
  54.                 p.type = NSClassFromString(propertyType);// p.type = @"NSString"
  55.                 p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound); //判断是否是可变的对象
  56.                 p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];//是否是该框架兼容的类型
  57.                 //存在协议(数组,也就是嵌套模型)
  58.                 while ([scanner scanString:@"<" intoString:NULL]) {
  59.                     NSString* protocolName = nil;
  60.                     [scanner scanUpToString:@">" intoString: &protocolName];
  61.                     if ([protocolName isEqualToString:@"Optional"]) {
  62.                         p.isOptional = YES;
  63.                     } else if([protocolName isEqualToString:@"Index"]) {
  64. #pragma GCC diagnostic push
  65. #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
  66.                         p.isIndex = YES;
  67. #pragma GCC diagnostic pop
  68.                         objc_setAssociatedObject(
  69.                                                  self.class,
  70.                                                  &kIndexPropertyNameKey,
  71.                                                  p.name,
  72.                                                  OBJC_ASSOCIATION_RETAIN // This is atomic
  73.                                                  );
  74.                     } else if([protocolName isEqualToString:@"Ignore"]) {
  75.                         p = nil;
  76.                     } else {
  77.                         p.protocol = protocolName;
  78.                     }
  79.                     //到最接近的>为止
  80.                     [scanner scanString:@">" intoString:NULL];
  81.                 }
  82.             }            
  83.             else if ([scanner scanString:@"{" intoString: &propertyType])               
  84.                 //属性是结构体
  85.                 [scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet]
  86.                                     intoString:&propertyType];
  87.                 p.isStandardJSONType = NO;
  88.                 p.structName = propertyType;
  89.             }
  90.             else {
  91.                 //属性是基本类型:Tq,N,V_age
  92.                 [scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@","]
  93.                                         intoString:&propertyType];
  94.                 //propertyType:q
  95.                 propertyType = valueTransformer.primitivesNames[propertyType];              
  96.                 //propertyType:long
  97.                 //基本类型数组
  98.                 if (![allowedPrimitiveTypes containsObject:propertyType]) {
  99.                     //类型不支持
  100.                     @throw [NSException exceptionWithName:@"JSONModelProperty type not allowed"
  101.                                                    reason:[NSString stringWithFormat:@"Property type of %@.%@ is not supported by JSONModel.", self.class, p.name]
  102.                                                  userInfo:nil];
  103.                 }
  104.             }
  105.             NSString *nsPropertyName = @(propertyName);            
  106.             //可选的
  107.             if([[self class] propertyIsOptional:nsPropertyName]){
  108.                 p.isOptional = YES;
  109.             }
  110.             //可忽略的
  111.             if([[self class] propertyIsIgnored:nsPropertyName]){
  112.                 p = nil;
  113.             }
  114.             //集合类
  115.             Class customClass = [[self class] classForCollectionProperty:nsPropertyName];            
  116.             if (customClass) {
  117.                 p.protocol = NSStringFromClass(customClass);
  118.             }
  119.             //忽略block
  120.             if ([propertyType isEqualToString:@"Block"]) {
  121.                 p = nil;
  122.             }
  123.             //如果字典里不存在,则添加到属性字典里(终于添加上去了。。。)
  124.             if (p && ![propertyIndex objectForKey:p.name]) {
  125.                 [propertyIndex setValue:p forKey:p.name];
  126.             }
  127.             //setter 和 getter
  128.             if (p)
  129.             {   //name ->Name
  130.                 NSString *name = [p.name stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[p.name substringToIndex:1].uppercaseString];
  131.                 // getter
  132.                 SEL getter = NSSelectorFromString([NSString stringWithFormat:@"JSONObjectFor%@", name]);
  133.                 if ([self respondsToSelector:getter])
  134.                     p.customGetter = getter;
  135.                 // setters
  136.                 p.customSetters = [NSMutableDictionary new];
  137.                 SEL genericSetter = NSSelectorFromString([NSString stringWithFormat:@"set%@WithJSONObject:", name]);
  138.                 if ([self respondsToSelector:genericSetter])
  139.                     p.customSetters[@"generic"] = [NSValue valueWithBytes:&genericSetter objCType:@encode(SEL)];
  140.                 for (Class type in allowedJSONTypes)
  141.                 {
  142.                     NSString *class = NSStringFromClass([JSONValueTransformer classByResolvingClusterClasses:type]);
  143.                     if (p.customSetters[class])
  144.                         continue;
  145.                     SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@With%@:", name, class]);
  146.                     if ([self respondsToSelector:setter])
  147.                         p.customSetters[class] = [NSValue valueWithBytes:&setter objCType:@encode(SEL)];
  148.                 }
  149.             }
  150.         }
  151.         free(properties);
  152.         //再指向自己的父类,知道等于JSONModel才停止
  153.         class = [class superclass];
  154.     }
  155.     //最后保存所有当前类,JSONModel的所有的父类的属性
  156.     objc_setAssociatedObject(
  157.                              self.class,
  158.                              &kClassPropertiesKey,
  159.                              [propertyIndex copy],
  160.                              OBJC_ASSOCIATION_RETAIN
  161.                              );
  162. }
复制代码
这是一串非常长的代码,先来大概解说一下这个方法:
这个方法用于检索JSONModel类中的属性,并将其转化为一个可用的 NSDictionary 对象。该方法会遍历模型类的属性,然后剖析每个属性的相关信息(如属性名、数据范例、对应的 JSON 字段名等),并将其存储在 NSDictionary 对象中,也就是上文的propertyIndex
如果我们具体分析代码流程,就会发现:


  • 该方法会先使用运行时函数获取JSONModel的属性列表
  1. objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
复制代码


  • 然后为每个属性创建一个JSONModelProperty 对象,也就是下文的p实例
  1. JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
复制代码


  • p实例也是我们创建的dictionary的value值
  1. if (p && ![propertyIndex objectForKey:p.name]) {
  2.         [propertyIndex setValue:p forKey:p.name];
  3. }
复制代码


  • JSONModelProperty 对象中包含了属性名、数据范例、对应的 JSON 字段名等信息,以下代码可见一斑
  1. p.type = NSClassFromString(propertyType);
  2. p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound);
  3. p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];
复制代码


  • 最后全部的这些 JSONModelProperty 对象都会存储在一个NSMutableDictionary 对象——propertyIndex中,然后通过objc_setAssociatedObject与模型举行关联,这一步是由于先前没有设置关联,如果不是第一次实例化这个类就不会调用__inspectProperties方法
  1. //最后保存所有当前类,JSONModel的所有的父类的属性
  2. objc_setAssociatedObject(
  3.         self.class,
  4.         &kClassPropertiesKey,
  5.         [propertyIndex copy],
  6.         OBJC_ASSOCIATION_RETAIN
  7. );
复制代码


  • 同时必要注意当前类会不断沿superclass继续链向上检索直到父类为JSONModel
  1. while (class != [JSONModel class]) {
  2.            ......................
  3.            ......................
  4.   class = [class superclass];
  5. }
复制代码
- (BOOL)__doesDictionaryNSDictionary*)dict matchModelWithKeyMapperJSONKeyMapper*)keyMapper

在这一步,我们先不看源码,我们直译一下这个方法:
字典是否与拥有KeyMapper的模型匹配
那么我们就很容易理解这个方法的作用,就是检查字典与模型是否匹配,在上一个方法中我们将
  1. //model类里面定义的属性集合是不能大于传入的字典里的key集合的。
  2. //如果存在了用户自定义的mapper,则需要按照用户的定义来进行转换。
  3. //(例如将gender转换为了sex)。
  4. -(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
  5. {
  6.     //check if all required properties are present
  7.     //拿到字典里所有的key
  8.     NSArray* incomingKeysArray = [dict allKeys];
  9.     NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;
  10.     //从array拿到set
  11.     NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];
  12.     //transform the key names, if necessary
  13.     //如有必要,变换键名称
  14.     //如果用户自定义了mapper,则进行转换
  15.     if (keyMapper || globalKeyMapper) {
  16.         NSMutableSet* transformedIncomingKeys = [NSMutableSet setWithCapacity: requiredProperties.count];
  17.         NSString* transformedName = nil;
  18.         //loop over the required properties list
  19.         //在所需属性列表上循环
  20.         //遍历需要转换的属性列表
  21.         for (JSONModelClassProperty* property in [self __properties__])
  22. {
  23.             //被转换成的属性名称(例如)TestModel(模型内) -> url(字典内)
  24.             transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
  25.             //check if exists and if so, add to incoming keys
  26.             //检查是否存在,如果存在,则添加到传入密钥
  27.             //(例如)拿到url以后,查看传入的字典里是否有url对应的值
  28.             id value;
  29.             @try {
  30.                 value = [dict valueForKeyPath:transformedName];
  31.             }
  32.             @catch (NSException *exception) {
  33.                 value = dict[transformedName];
  34.             }
  35.             if (value) {
  36.                 [transformedIncomingKeys addObject: property.name];
  37.             }
  38.         }
  39.         //overwrite the raw incoming list with the mapped key names
  40.         //用映射的键名称覆盖原始传入列表
  41.         incomingKeys = transformedIncomingKeys;
  42.     }
  43.     //check for missing input keys
  44.     //检查是否缺少输入键
  45.     //查看当前的model的属性的集合是否大于传入的属性集合,如果是,则返回错误
  46.     //也就是说模型类里的属性是不能多于传入字典里的key的,例如:
  47.     if (![requiredProperties isSubsetOfSet:incomingKeys]) {
  48.         //get a list of the missing properties
  49.         //获取缺失属性的列表(获取多出来的属性)
  50.         [requiredProperties minusSet:incomingKeys];
  51.         //not all required properties are in - invalid input
  52.         //并非所有必需的属性都在 in - 输入无效
  53.         JMLog(@"Incoming data was invalid [%@ initWithDictionary:]. Keys missing: %@", self.class, requiredProperties);
  54.         if (err) *err = [JSONModelError errorInvalidDataWithMissingKeys:requiredProperties];
  55.         return NO;
  56.     }
  57.     //not needed anymore
  58.     //不再需要了,释放掉
  59.     incomingKeys= nil;
  60.     requiredProperties= nil;
  61.     return YES;
  62. }
复制代码


  • 首先获取dict中全部的键名
  1.     NSArray* incomingKeysArray = [dict allKeys];
复制代码


  • 其次获取模型类中全部的属性名
  1. NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;
复制代码
这里跳进__requiredPropertyNames方法看一下:
  1. -(NSMutableSet*)__requiredPropertyNames
  2. {
  3.     //fetch the associated property names
  4.     NSMutableSet* classRequiredPropertyNames = objc_getAssociatedObject(self.class, &kClassRequiredPropertyNamesKey);
  5.     if (!classRequiredPropertyNames) {
  6.         classRequiredPropertyNames = [NSMutableSet set];
  7.         [[self __properties__] enumerateObjectsUsingBlock:^(JSONModelClassProperty* p, NSUInteger idx, BOOL *stop) {
  8.             if (!p.isOptional) [classRequiredPropertyNames addObject:p.name];
  9.         }];
  10.         //persist the list
  11.         objc_setAssociatedObject(
  12.                                  self.class,
  13.                                  &kClassRequiredPropertyNamesKey,
  14.                                  classRequiredPropertyNames,
  15.                                  OBJC_ASSOCIATION_RETAIN // This is atomic
  16.                                  );
  17.     }
  18.     return classRequiredPropertyNames;
  19. }
复制代码
我们先前说了,kClassRequiredPropertyNamesKey是一个用来保存全部属性的名称NSSet,因此调用这个方法可以让requiredProperties获取模型类中的全部属性名


  • 将dict中得到的key数组转换为set范例
  1.     NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];
复制代码


  • 如果存在keyMapper或是globalKeyMapper,则将模型中的属性名转换为KeyMapper中对应的Value,也就是将JSONModel中的属性名转换为Json键名
  1. transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
复制代码
具体怎样转换可以跳进-(NSString*)__mapStringNSString*)string withKeyMapperJSONKeyMapper*)keyMapper方法查看


  • 最后更新dict的键名集合,这样做是为了包管dict中的每个键名都有对应的有效值,而不是仅仅只有一个key键
  1. incomingKeys = transformedIncomingKeys;
复制代码


  • 最后查看模型类的属性列表是否为JSON键值集合的子集
    如果不是则会报错,意味着JSON中的数据不能完全覆盖我们声明的属性,说明我们有属性得不到赋值,因此会判定出错
  1.     //check for missing input keys
  2.     if (![requiredProperties isSubsetOfSet:incomingKeys]) {
  3.         //get a list of the missing properties
  4.         [requiredProperties minusSet:incomingKeys];
  5.         //not all required properties are in - invalid input
  6.         JMLog(@"Incoming data was invalid [%@ initWithDictionary:]. Keys missing: %@", self.class, requiredProperties);
  7.         if (err) *err = [JSONModelError errorInvalidDataWithMissingKeys:requiredProperties];
  8.         return NO;
  9.     }
复制代码
- (BOOL)__importDictionaryNSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err

前面举行了许多验证的操纵,终于到了将JSON数据导入模型中的操纵了
  1. //作者在最后给属性赋值的时候使用的是kvc的setValue:ForKey:的方法。
  2. //作者判断了模型里的属性的类型是否是JSONModel的子类,可见作者的考虑是非常周全的。
  3. //整个框架看下来,有很多的地方涉及到了错误判断,作者将将错误类型单独抽出一个类(JSONModelError),里面支持的错误类型很多,可以侧面反应作者思维之缜密。而且这个做法也可以在我们写自己的框架或者项目中使用。
  4. //从字典里获取值并赋给当前模型对象
  5. -(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
  6. {
  7.     //loop over the incoming keys and set self's properties
  8.     //遍历保存的所有属性的字典
  9.     for (JSONModelClassProperty* property in [self __properties__])
  10. {
  11.         //convert key name to model keys, if a mapper is provided
  12.         //将属性的名称(若有改动就拿改后的名称)拿过来,作为key,用这个key来查找传进来的字典里对应的值
  13.         NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
  14.         //JMLog(@"keyPath: %@", jsonKeyPath);
  15.         //general check for data type compliance
  16.         //用来保存从字典里获取的值
  17.         id jsonValue;
  18.         @try {
  19.             jsonValue = [dict valueForKeyPath: jsonKeyPath];
  20.         }
  21.         @catch (NSException *exception) {
  22.             jsonValue = dict[jsonKeyPath];
  23.         }
  24.         //check for Optional properties
  25.         //检查可选属性
  26.         //字典不存在对应的key
  27.         if (isNull(jsonValue)) {
  28.             //skip this property, continue with next property
  29.             //跳过此属性,继续下一个属性
  30.             //如果这个key是可以不存在的
  31.             if (property.isOptional || !validation) continue;
  32.             //如果这个key是必须有的,则返回错误
  33.             if (err) {
  34.                 //null value for required property
  35.                 //所需属性的值为null
  36.                 NSString* msg = [NSString stringWithFormat:@"Value of required model key %@ is null", property.name];
  37.                 JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
  38.                 *err = [dataErr errorByPrependingKeyPathComponent:property.name];
  39.             }
  40.             return NO;
  41.         }
  42.         //获取,取到的值的类型
  43.         Class jsonValueClass = [jsonValue class];
  44.         BOOL isValueOfAllowedType = NO;
  45.         //查看是否是本框架兼容的属性类型
  46.         for (Class allowedType in allowedJSONTypes) {
  47.             if ( [jsonValueClass isSubclassOfClass: allowedType] ) {
  48.                 isValueOfAllowedType = YES;
  49.                 break;
  50.             }
  51.         }
  52.         
  53.         //如果不兼容,则返回NO,mapping失败,抛出错误
  54.         if (isValueOfAllowedType==NO) {
  55.             //type not allowed
  56.             JMLog(@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass));
  57.             if (err) {
  58.                 NSString* msg = [NSString stringWithFormat:@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass)];
  59.                 JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
  60.                 *err = [dataErr errorByPrependingKeyPathComponent:property.name];
  61.             }
  62.             return NO;
  63.         }
  64.         //check if there's matching property in the model
  65.         //检查模型中是否有匹配的属性
  66.         //如果是兼容的类型:
  67.         if (property) {
  68.             // check for custom setter, than the model doesn't need to do any guessing
  69.             // how to read the property's value from JSON
  70.             //检查自定义setter,则模型不需要进行任何猜测(查看是否有自定义setter,并设置)
  71.             //如何从JSON读取属性值
  72.             if ([self __customSetValue:jsonValue forProperty:property]) {
  73.                 //skip to next JSON key
  74.                 //跳到下一个JSON键
  75.                 continue;
  76.             };
  77.             // 0) handle primitives
  78.             //0)句柄原语
  79.             //基本类型
  80.             if (property.type == nil && property.structName==nil) {
  81.                 //generic setter
  82.                 //通用setter
  83.                 //kvc赋值
  84.                 if (jsonValue != [self valueForKey:property.name]) {
  85.                     [self setValue:jsonValue forKey: property.name];
  86.                 }
  87.                 //skip directly to the next key
  88.                 //直接跳到下一个键
  89.                 continue;
  90.             }
  91.             // 0.5) handle nils
  92.             //如果传来的值是空,即使当前的属性对应的值不是空,也要将空值赋给它
  93.             if (isNull(jsonValue)) {
  94.                 if ([self valueForKey:property.name] != nil) {
  95.                     [self setValue:nil forKey: property.name];
  96.                 }
  97.                 continue;
  98.             }
  99.             // 1) check if property is itself a JSONModel
  100.             //检查属性本身是否是jsonmodel类型
  101.             if ([self __isJSONModelSubClass:property.type]) {
  102.                 //initialize the property's model, store it
  103.                 //初始化属性的模型,并将其存储
  104.                 //通过自身的转模型方法,获取对应的值
  105.                 JSONModelError* initErr = nil;
  106.                 id value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];
  107.                 if (!value) {
  108.                     //skip this property, continue with next property
  109.                     //跳过此属性,继续下一个属性(如果该属性不是必须的,则略过)
  110.                     if (property.isOptional || !validation) continue;
  111.                     // Propagate the error, including the property name as the key-path component
  112.                     //传播错误,包括将属性名称作为密钥路径组件(如果该属性是必须的,则返回错误)
  113.                     if((err != nil) && (initErr != nil))
  114.                     {
  115.                         *err = [initErr errorByPrependingKeyPathComponent:property.name];
  116.                     }
  117.                     return NO;
  118.                 }
  119.                 //当前的属性值与value不同时,则赋值
  120.                 if (![value isEqual:[self valueForKey:property.name]]) {
  121.                     [self setValue:value forKey: property.name];
  122.                 }
  123.                 //for clarity, does the same without continue
  124.                 //为清楚起见,不继续执行相同操作
  125.                 continue;
  126.             } else {
  127.                 // 2) check if there's a protocol to the property
  128.                 //  ) might or not be the case there's a built in transform for it
  129.                 //2)检查是否有协议
  130.                 //)可能是,也可能不是,它有一个内置的转换
  131.                 if (property.protocol) {
  132.                     //JMLog(@"proto: %@", p.protocol);
  133.                     //转化为数组,这个数组就是例子中的friends属性
  134.                     jsonValue = [self __transform:jsonValue forProperty:property error:err];
  135.                     if (!jsonValue) {
  136.                         if ((err != nil) && (*err == nil)) {
  137.                             NSString* msg = [NSString stringWithFormat:@"Failed to transform value, but no error was set during transformation. (%@)", property];
  138.                             JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
  139.                             *err = [dataErr errorByPrependingKeyPathComponent:property.name];
  140.                         }
  141.                         return NO;
  142.                     }
  143.                 }
  144.                 // 3.1) handle matching standard JSON types
  145.                 //3.1)句柄匹配标准JSON类型
  146.                 //对象类型
  147.                 if (property.isStandardJSONType && [jsonValue isKindOfClass: property.type]) {
  148.                     //mutable properties
  149.                     //可变类型的属性
  150.                     if (property.isMutable) {
  151.                         jsonValue = [jsonValue mutableCopy];
  152.                     }
  153.                     //set the property value
  154.                     //为属性赋值
  155.                     if (![jsonValue isEqual:[self valueForKey:property.name]]) {
  156.                         [self setValue:jsonValue forKey: property.name];
  157.                     }
  158.                     continue;
  159.                 }
  160.                 // 3.3) handle values to transform
  161.                 //3.3)处理要转换的值
  162.                 //当前的值的类型与对应的属性的类型不一样的时候,需要查看用户是否自定义了转换器(例如从NSSet到NSArray转换:-(NSSet *)NSSetFromNSArray:(NSArray *)array)
  163.                 if (
  164.                     (![jsonValue isKindOfClass:property.type] && !isNull(jsonValue))
  165.                     ||
  166.                     //the property is mutable
  167.                     //属性是可变的
  168.                     property.isMutable
  169.                     ||
  170.                     //custom struct property
  171.                     //自定义结构属性
  172.                     property.structName
  173.                     ) {
  174.                     // searched around the web how to do this better
  175.                     // but did not find any solution, maybe that's the best idea? (hardly)
  176.                     //在网上搜索如何更好地做到这一点
  177.                     //但是没有找到任何解决方案,也许这是最好的主意?(几乎没有)
  178.                     Class sourceClass = [JSONValueTransformer classByResolvingClusterClasses:[jsonValue class]];
  179.                     //JMLog(@"to type: [%@] from type: [%@] transformer: [%@]", p.type, sourceClass, selectorName);
  180.                     //build a method selector for the property and json object classes
  181.                     //为属性和json对象类构建方法选择器
  182.                     NSString* selectorName = [NSString stringWithFormat:@"%@From%@:",
  183.                                               (property.structName? property.structName : property.type), //target name目标名
  184.                                               sourceClass]; //source name源名称
  185.                     SEL selector = NSSelectorFromString(selectorName);
  186.                     //check for custom transformer
  187.                     //查看自定义的转换器是否存在
  188.                     BOOL foundCustomTransformer = NO;
  189.                     if ([valueTransformer respondsToSelector:selector]) {
  190.                         foundCustomTransformer = YES;
  191.                     } else {
  192.                         //try for hidden custom transformer
  193.                         //尝试隐藏自定义转换器
  194.                         selectorName = [NSString stringWithFormat:@"__%@",selectorName];
  195.                         selector = NSSelectorFromString(selectorName);
  196.                         if ([valueTransformer respondsToSelector:selector]) {
  197.                             foundCustomTransformer = YES;
  198.                         }
  199.                     }
  200.                     //check if there's a transformer with that name
  201.                     //检查是否有同名变压器
  202.                     //如果存在自定义转换器,则进行转换
  203.                     if (foundCustomTransformer) {
  204.                         IMP imp = [valueTransformer methodForSelector:selector];
  205.                         id (*func)(id, SEL, id) = (void *)imp;
  206.                         jsonValue = func(valueTransformer, selector, jsonValue);
  207.                         if (![jsonValue isEqual:[self valueForKey:property.name]])
  208.                             [self setValue:jsonValue forKey:property.name];
  209.                     } else {
  210.                         //如果没有自定义转换器,返回错误
  211.                         if (err) {
  212.                             NSString* msg = [NSString stringWithFormat:@"%@ type not supported for %@.%@", property.type, [self class], property.name];
  213.                             JSONModelError* dataErr = [JSONModelError errorInvalidDataWithTypeMismatch:msg];
  214.                             *err = [dataErr errorByPrependingKeyPathComponent:property.name];
  215.                         }
  216.                         return NO;
  217.                     }
  218.                 } else {
  219.                     // 3.4) handle "all other" cases (if any)
  220.                     //3.4)处理“所有其他”情况(如有)
  221.                     if (![jsonValue isEqual:[self valueForKey:property.name]])
  222.                         [self setValue:jsonValue forKey:property.name];
  223.                 }
  224.             }
  225.         }
  226.     }
  227.     return YES;
  228. }
复制代码


  • 首先遍历模型类中的全部属性
  1. for (JSONModelClassProperty* property in [self __properties__])
复制代码


  • 找到JSON字典中与模型类对应的key
  1.         NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
复制代码


  • 获取keyvalue,这里不用担心获取为空,由于在上一步验证dict是否与keymapper有匹配的方法中已经验证过了
  1. id jsonValue;
  2.         @try {
  3.             jsonValue = [dict valueForKeyPath: jsonKeyPath];
  4.         }
  5.         @catch (NSException *exception) {
  6.             jsonValue = dict[jsonKeyPath];
  7.         }
复制代码


  • 举行一系列检查操纵,就不讲了,最后终于通过KVC将JSON字典中的值赋给了模型类
  1.             if (property.type == nil && property.structName==nil) {
  2.                 //generic setter
  3.                 //通用setter
  4.                 //kvc赋值
  5.                 if (jsonValue != [self valueForKey:property.name]) {
  6.                     [self setValue:jsonValue forKey: property.name];
  7.                 }
  8.                 //skip directly to the next key
  9.                 //直接跳到下一个键
  10.                 continue;
  11.             }
复制代码
总结

JSONModel提供了一种方法,


  • 得到的JSON数据自动与Model举行匹配
  • 还提供了keyMapper将JSON键自动映射到模型的属性
  • 还让我们可以自界说错误处理
这里再讲一下JSONModel的实现流程:
方法中通过获取JSONModel类的属性列表,与传入的JSON数据自动匹配,同时还可以通过KeyMapper修改不类似的映射,如果模型类与JSON数据字段不匹配则会抛出错误(这里体现为Model中某些必须的属性没有在JSON数据中找到相应的映射),最后如果范例等都检查成功,则通过KVC将JSON数据中的value设置在Model类的对应的属性上
  1. if (jsonValue != [self valueForKey:property.name]) {
  2.                     [self setValue:jsonValue forKey: property.name];
复制代码
这里再总结一下JSONModel的几个优点


  • 首先就是Runtime中动态剖析Model数据范例,可以实现自动匹配
  • 然后在setup方法中还通过AssociatedObject实现了已经剖析过了Model的属性列表等必须信息的缓存,避免了类似模型的重复剖析
  • 还有就是KVC附值
  • 使用KeyMapper修改不同等的JSON字段与属性
这里还有必要再讲一下KeyMapper,你可以通过重写模型类中的 +keyMapper 方法来自界说键映射。这个方法必要返回一个 JSONKeyMapper 对象,该对象通过初始化方法接收一个字典来界说映射关系。
  1. @interface User : JSONModel
  2. @property (strong, nonatomic) NSString *userId;
  3. @property (strong, nonatomic) NSString *email;
  4. @property (strong, nonatomic) NSString *userDescription;
  5. @end
  6. @implementation User
  7. + (JSONKeyMapper *)keyMapper {
  8.     return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
  9.         @"userId": @"id",
  10.         @"email": @"email_address",
  11.         @"userDescription": @"description"
  12.     }];
  13. }
  14. @end
复制代码
参考博客:
JSONModel源码剖析
【iOS-JSONModel源码】
JSONModel源代码剖析

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

老婆出轨

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表