【iOS】JSONModel源码阅读笔记

文章目录

  • 前言
  • 一、JSONModel使用
  • 二、JSONModel其他方法
    • 转换属性名称
  • 三、源码分析
    • - (instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err
      • [self init]
      • __setup__
      • __inspectProperties
    • - (BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper
    • - (BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
  • 总结


前言

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

一、JSONModel使用

之前已经详细写过使用,有一些没写到的使用会在后面补充
【iOS】JSONModel的基本使用

二、JSONModel其他方法

转换属性名称

有时候我们的JSONModel的属性名称与传入的字典的键值不对应,我们就需要使用keyMapper来转换JSON键,将模型中的属性转换为KeyMapper中对应的键名dictionary中的key值为属性名,value为json键名

#import <JSONModel/JSONModel.h>

@interface UserProfile : JSONModel

@property (strong, nonatomic) NSString* userId;
@property (strong, nonatomic) NSString* emailAddress;
@property (strong, nonatomic) NSString* firstName;
@property (strong, nonatomic) NSString* lastName;

@end

设置键映射
为了将JSON键映射到正确的模型属性,你需要在模型类中重写+ (JSONKeyMapper *)keyMapper方法,指定如何将模型中的属性转换为JSON中的键名

@implementation UserProfile

// 使用 JSONKeyMapper 来定义 JSON 字段与模型属性之间的映射关系
+ (JSONKeyMapper *)keyMapper {
    return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
        @"userId": @"user_id",
        @"emailAddress": @"email_address",
        @"firstName": @"profile_info.first_name",
        @"lastName": @"profile_info.last_name"
    }];
}
@end

三、源码分析

先看流程图
在这里插入图片描述

看一下源代码的目录,可以看到以下内容
在这里插入图片描述

在JSONModel中提供了四种初始化方法

-(instancetype)initWithString:(NSString*)string error:(JSONModelError**)err;
-(instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError**)err;
-(instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err;
-(instancetype)initWithData:(NSData *)data error:(NSError **)error;

四种方法大同小异,我们从最经典的-(instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err;讲起

- (instancetype)initWithDictionary:(NSDictionary*)dict error:(NSError **)err

还是先查看源代码

-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{
    //方法1. 参数为nil
    if (!dict) {
        if (err) *err = [JSONModelError errorInputIsNil];
        return nil;
    }
    //方法2. 参数不是nil,但也不是字典
    if (![dict isKindOfClass:[NSDictionary class]]) {
        if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];
        return nil;
    }
    //方法3. 初始化
    self = [self init];
    if (!self) {
        //初始化失败
        if (err) *err = [JSONModelError errorModelIsInvalid];
        return nil;
    }
    //方法4. 检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO)
    if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
        return nil;
    }
    //方法5. 核心方法:字典的key与模型的属性的映射
    if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
        return nil;
    }
    //方法6. 可以重写[self validate:err]方法并返回NO,让用户自定义错误并阻拦model的返回
    if (![self validate:err]) {
        return nil;
    }
    //方法7. 终于通过了!成功返回model
    return self;
}

可以看到源代码分为了七步,我们来逐步讲解

  • 第一步:首先进行判错操作,查询参数是否为空,如果为空则直接返回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]

先来看一下第三步的源代码

self = [self init];
    if (!self) {
        //super init didn't succeed
        if (err) *err = [JSONModelError errorModelIsInvalid];
        return nil;
    }

[self init]的代码调用了init初始化函数,实现如下,我们主要关注的是其中[self setup]

- (id)init
{
    self = [super init];
    if (self) {
        //do initial class setup
        [self __setup__];
    }
    return self;
}

setup

来看一下setup的实现

- (void)__setup__
{
    //if first instance of this model, generate the property list
    // 如果是该模型的第一个实例,则生成属性列表
    if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {
        [self __inspectProperties];
    }

    //if there's a custom key mapper, store it in the associated object
    id mapper = [[self class] keyMapper];
    if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
        objc_setAssociatedObject(
                                 self.class,
                                 &kMapperObjectKey,
                                 mapper,
                                 OBJC_ASSOCIATION_RETAIN // This is atomic
                                 );
    }
}

我们来逐步分析
第一句代码(!objc_getAssociatedObject(self.class, &kClassPropertiesKey))查看当前类是否存在关联对象,如果是该模型的第一个实例,则生成属性列表,也就是调用[self __inspectProperties];检索属性

检索完之后如果存在keyMapperkeyMapper也与模型类进行关联

if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
        objc_setAssociatedObject(
                                 self.class,
                                 &kMapperObjectKey,
                                 mapper,
                                 OBJC_ASSOCIATION_RETAIN // This is atomic
                                 );
    }

__inspectProperties

-(void)__inspectProperties
{
//    最终保存所有属性的字典,形式为:
//    {
//        age = "@property primitive age (Setters = [])";
//        friends = "@property NSArray* friends (Standard JSON type, Setters = [])";
//        gender = "@property NSString* gender (Standard JSON type, Setters = [])";
//        name = "@property NSString* name (Standard JSON type, Setters = [])";
//    }
    NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary];
    //获取当前的类名
    Class class = [self class];    
    NSScanner* scanner = nil;
    NSString* propertyType = nil;
    // 循环条件:当class 是 JSONModel自己的时候终止
    while (class != [JSONModel class]) {        
        //属性的个数
        unsigned int propertyCount;
        //获得属性列表(所有@property声明的属性)
        objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
        //遍历所有的属性
        for (unsigned int i = 0; i < propertyCount; i++) {
            //获得属性名称
            objc_property_t property = properties[i];//获得当前的属性
            const char *propertyName = property_getName(property);//name(C字符串)            
            //JSONModel里的每一个属性,都被封装成一个JSONModelClassProperty对象
            JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
            p.name = @(propertyName);//propertyName:属性名称,例如:name,age,gender
            //获得属性类型
            const char *attrs = property_getAttributes(property);
            NSString* propertyAttributes = @(attrs);
            // T@\"NSString\",C,N,V_name
            // Tq,N,V_age
            // T@\"NSString\",C,N,V_gender
            // T@"NSArray",&,N,V_friends            
            NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];
            //说明是只读属性,不做任何操作
            if ([attributeItems containsObject:@"R"]) {
                continue; //to next property
            }
            //检查出是布尔值
            if ([propertyAttributes hasPrefix:@"Tc,"]) {
                p.structName = @"BOOL";//使其变为结构体
            }            
            //实例化一个scanner
            scanner = [NSScanner scannerWithString: propertyAttributes];
            [scanner scanUpToString:@"T" intoString: nil];
            [scanner scanString:@"T" intoString:nil];
            //http://blog.csdn.net/kmyhy/article/details/8258858           
            if ([scanner scanString:@"@\"" intoString: &propertyType]) {                
                 //属性是一个对象
                [scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"<"]
                                        intoString:&propertyType];//propertyType -> NSString                
                p.type = NSClassFromString(propertyType);// p.type = @"NSString"
                p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound); //判断是否是可变的对象
                p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];//是否是该框架兼容的类型
                //存在协议(数组,也就是嵌套模型)
                while ([scanner scanString:@"<" intoString:NULL]) {
                    NSString* protocolName = nil;
                    [scanner scanUpToString:@">" intoString: &protocolName];
                    if ([protocolName isEqualToString:@"Optional"]) {
                        p.isOptional = YES;
                    } else if([protocolName isEqualToString:@"Index"]) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
                        p.isIndex = YES;
#pragma GCC diagnostic pop
                        objc_setAssociatedObject(
                                                 self.class,
                                                 &kIndexPropertyNameKey,
                                                 p.name,
                                                 OBJC_ASSOCIATION_RETAIN // This is atomic
                                                 );
                    } else if([protocolName isEqualToString:@"Ignore"]) {
                        p = nil;
                    } else {
                        p.protocol = protocolName;
                    }
                    //到最接近的>为止
                    [scanner scanString:@">" intoString:NULL];
                }
            }            
            else if ([scanner scanString:@"{" intoString: &propertyType])                
                //属性是结构体
                [scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet]
                                    intoString:&propertyType];
                p.isStandardJSONType = NO;
                p.structName = propertyType;
            }
            else {
                //属性是基本类型:Tq,N,V_age
                [scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@","]
                                        intoString:&propertyType];
                //propertyType:q
                propertyType = valueTransformer.primitivesNames[propertyType];              
                //propertyType:long
                //基本类型数组
                if (![allowedPrimitiveTypes containsObject:propertyType]) {
                    //类型不支持
                    @throw [NSException exceptionWithName:@"JSONModelProperty type not allowed"
                                                   reason:[NSString stringWithFormat:@"Property type of %@.%@ is not supported by JSONModel.", self.class, p.name]
                                                 userInfo:nil];
                }
            }
            NSString *nsPropertyName = @(propertyName);            
            //可选的
            if([[self class] propertyIsOptional:nsPropertyName]){
                p.isOptional = YES;
            }
            //可忽略的
            if([[self class] propertyIsIgnored:nsPropertyName]){
                p = nil;
            }
            //集合类
            Class customClass = [[self class] classForCollectionProperty:nsPropertyName];            
            if (customClass) {
                p.protocol = NSStringFromClass(customClass);
            }
            //忽略block
            if ([propertyType isEqualToString:@"Block"]) {
                p = nil;
            }
            //如果字典里不存在,则添加到属性字典里(终于添加上去了。。。)
            if (p && ![propertyIndex objectForKey:p.name]) {
                [propertyIndex setValue:p forKey:p.name];
            }
            //setter 和 getter
            if (p)
            {   //name ->Name
                NSString *name = [p.name stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[p.name substringToIndex:1].uppercaseString];
                // getter
                SEL getter = NSSelectorFromString([NSString stringWithFormat:@"JSONObjectFor%@", name]);
                if ([self respondsToSelector:getter])
                    p.customGetter = getter;
                // setters
                p.customSetters = [NSMutableDictionary new];
                SEL genericSetter = NSSelectorFromString([NSString stringWithFormat:@"set%@WithJSONObject:", name]);
                if ([self respondsToSelector:genericSetter])
                    p.customSetters[@"generic"] = [NSValue valueWithBytes:&genericSetter objCType:@encode(SEL)];
                for (Class type in allowedJSONTypes)
                {
                    NSString *class = NSStringFromClass([JSONValueTransformer classByResolvingClusterClasses:type]);
                    if (p.customSetters[class])
                        continue;
                    SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@With%@:", name, class]);
                    if ([self respondsToSelector:setter])
                        p.customSetters[class] = [NSValue valueWithBytes:&setter objCType:@encode(SEL)];
                }
            }
        }
        free(properties);
        //再指向自己的父类,知道等于JSONModel才停止
        class = [class superclass];
    }
    //最后保存所有当前类,JSONModel的所有的父类的属性
    objc_setAssociatedObject(
                             self.class,
                             &kClassPropertiesKey,
                             [propertyIndex copy],
                             OBJC_ASSOCIATION_RETAIN
                             );
}

这是一串十分长的代码,先来大概讲解一下这个方法:
这个方法用于检索JSONModel类中的属性,并将其转化为一个可用的 NSDictionary 对象。该方法会遍历模型类的属性,然后解析每个属性的相关信息(如属性名、数据类型、对应的 JSON 字段名等),并将其存储在 NSDictionary 对象中,也就是上文的propertyIndex

如果我们具体分析代码流程,就会发现:

  • 该方法会先使用运行时函数获取JSONModel的属性列表
objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
  • 然后为每个属性创建一个JSONModelProperty 对象,也就是下文的p实例
JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
  • p实例也是我们创建的dictionaryvalue
if (p && ![propertyIndex objectForKey:p.name]) {
	[propertyIndex setValue:p forKey:p.name];
}
  • JSONModelProperty 对象中包含了属性名、数据类型、对应的 JSON 字段名等信息,以下代码可见一斑
p.type = NSClassFromString(propertyType);
p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound);
p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];
  • 最后所有的这些 JSONModelProperty 对象都会存储在一个NSMutableDictionary 对象——propertyIndex中,然后通过objc_setAssociatedObject与模型进行关联,这一步是因为先前没有设置关联,如果不是第一次实例化这个类就不会调用__inspectProperties方法
//最后保存所有当前类,JSONModel的所有的父类的属性
objc_setAssociatedObject(
	self.class,
	&kClassPropertiesKey,
	[propertyIndex copy],
	OBJC_ASSOCIATION_RETAIN
);
  • 同时需要注意当前类会不断沿superclass继承链向上检索直到父类为JSONModel
 while (class != [JSONModel class]) {
 	   ......................
 	   ......................
  class = [class superclass];
 }

- (BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper

在这一步,我们先不看源码,我们直译一下这个方法:
字典是否与拥有KeyMapper的模型匹配

那么我们就很容易理解这个方法的作用,就是检查字典与模型是否匹配,在上一个方法中我们将

//model类里面定义的属性集合是不能大于传入的字典里的key集合的。
//如果存在了用户自定义的mapper,则需要按照用户的定义来进行转换。
//(例如将gender转换为了sex)。
-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{
    //check if all required properties are present
    //拿到字典里所有的key
    NSArray* incomingKeysArray = [dict allKeys];
    NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;
    //从array拿到set
    NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];

    //transform the key names, if necessary
    //如有必要,变换键名称
    //如果用户自定义了mapper,则进行转换
    if (keyMapper || globalKeyMapper) {

        NSMutableSet* transformedIncomingKeys = [NSMutableSet setWithCapacity: requiredProperties.count];
        NSString* transformedName = nil;

        //loop over the required properties list
        //在所需属性列表上循环
        //遍历需要转换的属性列表
        for (JSONModelClassProperty* property in [self __properties__]) {
            //被转换成的属性名称(例如)TestModel(模型内) -> url(字典内)
            transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;

            //check if exists and if so, add to incoming keys
            //检查是否存在,如果存在,则添加到传入密钥
            //(例如)拿到url以后,查看传入的字典里是否有url对应的值
            id value;
            @try {
                value = [dict valueForKeyPath:transformedName];
            }
            @catch (NSException *exception) {
                value = dict[transformedName];
            }

            if (value) {
                [transformedIncomingKeys addObject: property.name];
            }
        }

        //overwrite the raw incoming list with the mapped key names
        //用映射的键名称覆盖原始传入列表
        incomingKeys = transformedIncomingKeys;
    }

    //check for missing input keys
    //检查是否缺少输入键
    //查看当前的model的属性的集合是否大于传入的属性集合,如果是,则返回错误
    //也就是说模型类里的属性是不能多于传入字典里的key的,例如:
    if (![requiredProperties isSubsetOfSet:incomingKeys]) {

        //get a list of the missing properties
        //获取缺失属性的列表(获取多出来的属性)
        [requiredProperties minusSet:incomingKeys];

        //not all required properties are in - invalid input
        //并非所有必需的属性都在 in - 输入无效
        JMLog(@"Incoming data was invalid [%@ initWithDictionary:]. Keys missing: %@", self.class, requiredProperties);

        if (err) *err = [JSONModelError errorInvalidDataWithMissingKeys:requiredProperties];
        return NO;
    }

    //not needed anymore
    //不再需要了,释放掉
    incomingKeys= nil;
    requiredProperties= nil;

    return YES;
}
  • 首先获取dict中所有的键名
    NSArray* incomingKeysArray = [dict allKeys];
  • 其次获取模型类中所有的属性名
NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;

这里跳进__requiredPropertyNames方法看一下:

-(NSMutableSet*)__requiredPropertyNames
{
    //fetch the associated property names
    NSMutableSet* classRequiredPropertyNames = objc_getAssociatedObject(self.class, &kClassRequiredPropertyNamesKey);

    if (!classRequiredPropertyNames) {
        classRequiredPropertyNames = [NSMutableSet set];
        [[self __properties__] enumerateObjectsUsingBlock:^(JSONModelClassProperty* p, NSUInteger idx, BOOL *stop) {
            if (!p.isOptional) [classRequiredPropertyNames addObject:p.name];
        }];

        //persist the list
        objc_setAssociatedObject(
                                 self.class,
                                 &kClassRequiredPropertyNamesKey,
                                 classRequiredPropertyNames,
                                 OBJC_ASSOCIATION_RETAIN // This is atomic
                                 );
    }
    return classRequiredPropertyNames;
}

我们先前说了,kClassRequiredPropertyNamesKey是一个用来保存所有属性的名称NSSet,因此调用这个方法可以让requiredProperties获取模型类中的所有属性名

  • 将dict中得到的key数组转换为set类型
    NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];
  • 如果存在keyMapper或是globalKeyMapper,则将模型中的属性名转换为KeyMapper中对应的Value,也就是将JSONModel中的属性名转换为Json键名
transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;

具体如何转换可以跳进-(NSString*)__mapString:(NSString*)string withKeyMapper:(JSONKeyMapper*)keyMapper方法查看

  • 最后更新dict的键名集合,这样做是为了保证dict中的每个键名都有对应的有效值,而不是仅仅只有一个key键
incomingKeys = transformedIncomingKeys;
  • 最后查看模型类的属性列表是否为JSON键值集合的子集
    如果不是则会报错,意味着JSON中的数据不能完全覆盖我们声明的属性,说明我们有属性得不到赋值,因此会判断出错
    //check for missing input keys
    if (![requiredProperties isSubsetOfSet:incomingKeys]) {

        //get a list of the missing properties
        [requiredProperties minusSet:incomingKeys];

        //not all required properties are in - invalid input
        JMLog(@"Incoming data was invalid [%@ initWithDictionary:]. Keys missing: %@", self.class, requiredProperties);

        if (err) *err = [JSONModelError errorInvalidDataWithMissingKeys:requiredProperties];
        return NO;
    }

- (BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err

前面进行了许多验证的操作,终于到了将JSON数据导入模型中的操作了

//作者在最后给属性赋值的时候使用的是kvc的setValue:ForKey:的方法。
//作者判断了模型里的属性的类型是否是JSONModel的子类,可见作者的考虑是非常周全的。
//整个框架看下来,有很多的地方涉及到了错误判断,作者将将错误类型单独抽出一个类(JSONModelError),里面支持的错误类型很多,可以侧面反应作者思维之缜密。而且这个做法也可以在我们写自己的框架或者项目中使用。
//从字典里获取值并赋给当前模型对象
-(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
{
    //loop over the incoming keys and set self's properties
    //遍历保存的所有属性的字典
    for (JSONModelClassProperty* property in [self __properties__]) {

        //convert key name to model keys, if a mapper is provided
        //将属性的名称(若有改动就拿改后的名称)拿过来,作为key,用这个key来查找传进来的字典里对应的值
        NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
        //JMLog(@"keyPath: %@", jsonKeyPath);

        //general check for data type compliance
        //用来保存从字典里获取的值
        id jsonValue;
        @try {
            jsonValue = [dict valueForKeyPath: jsonKeyPath];
        }
        @catch (NSException *exception) {
            jsonValue = dict[jsonKeyPath];
        }

        //check for Optional properties
        //检查可选属性
        //字典不存在对应的key
        if (isNull(jsonValue)) {
            //skip this property, continue with next property
            //跳过此属性,继续下一个属性
            //如果这个key是可以不存在的
            if (property.isOptional || !validation) continue;
            //如果这个key是必须有的,则返回错误
            if (err) {
                //null value for required property
                //所需属性的值为null
                NSString* msg = [NSString stringWithFormat:@"Value of required model key %@ is null", property.name];
                JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
                *err = [dataErr errorByPrependingKeyPathComponent:property.name];
            }
            return NO;
        }
        //获取,取到的值的类型
        Class jsonValueClass = [jsonValue class];
        BOOL isValueOfAllowedType = NO;
        //查看是否是本框架兼容的属性类型
        for (Class allowedType in allowedJSONTypes) {
            if ( [jsonValueClass isSubclassOfClass: allowedType] ) {
                isValueOfAllowedType = YES;
                break;
            }
        }
        
        //如果不兼容,则返回NO,mapping失败,抛出错误
        if (isValueOfAllowedType==NO) {
            //type not allowed
            JMLog(@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass));

            if (err) {
                NSString* msg = [NSString stringWithFormat:@"Type %@ is not allowed in JSON.", NSStringFromClass(jsonValueClass)];
                JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
                *err = [dataErr errorByPrependingKeyPathComponent:property.name];
            }
            return NO;
        }

        //check if there's matching property in the model
        //检查模型中是否有匹配的属性
        //如果是兼容的类型:
        if (property) {

            // check for custom setter, than the model doesn't need to do any guessing
            // how to read the property's value from JSON
            //检查自定义setter,则模型不需要进行任何猜测(查看是否有自定义setter,并设置)
            //如何从JSON读取属性值
            if ([self __customSetValue:jsonValue forProperty:property]) {
                //skip to next JSON key
                //跳到下一个JSON键
                continue;
            };

            // 0) handle primitives
            //0)句柄原语
            //基本类型
            if (property.type == nil && property.structName==nil) {

                //generic setter
                //通用setter
                //kvc赋值
                if (jsonValue != [self valueForKey:property.name]) {
                    [self setValue:jsonValue forKey: property.name];
                }

                //skip directly to the next key
                //直接跳到下一个键
                continue;
            }

            // 0.5) handle nils
            //如果传来的值是空,即使当前的属性对应的值不是空,也要将空值赋给它
            if (isNull(jsonValue)) {
                if ([self valueForKey:property.name] != nil) {
                    [self setValue:nil forKey: property.name];
                }
                continue;
            }


            // 1) check if property is itself a JSONModel
            //检查属性本身是否是jsonmodel类型
            if ([self __isJSONModelSubClass:property.type]) {

                //initialize the property's model, store it
                //初始化属性的模型,并将其存储
                //通过自身的转模型方法,获取对应的值
                JSONModelError* initErr = nil;
                id value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];

                if (!value) {
                    //skip this property, continue with next property
                    //跳过此属性,继续下一个属性(如果该属性不是必须的,则略过)
                    if (property.isOptional || !validation) continue;

                    // Propagate the error, including the property name as the key-path component
                    //传播错误,包括将属性名称作为密钥路径组件(如果该属性是必须的,则返回错误)
                    if((err != nil) && (initErr != nil))
                    {
                        *err = [initErr errorByPrependingKeyPathComponent:property.name];
                    }
                    return NO;
                }
                //当前的属性值与value不同时,则赋值
                if (![value isEqual:[self valueForKey:property.name]]) {
                    [self setValue:value forKey: property.name];
                }

                //for clarity, does the same without continue
                //为清楚起见,不继续执行相同操作
                continue;

            } else {

                // 2) check if there's a protocol to the property
                //  ) might or not be the case there's a built in transform for it
                //2)检查是否有协议
                //)可能是,也可能不是,它有一个内置的转换
                if (property.protocol) {

                    //JMLog(@"proto: %@", p.protocol);
                    //转化为数组,这个数组就是例子中的friends属性
                    jsonValue = [self __transform:jsonValue forProperty:property error:err];
                    if (!jsonValue) {
                        if ((err != nil) && (*err == nil)) {
                            NSString* msg = [NSString stringWithFormat:@"Failed to transform value, but no error was set during transformation. (%@)", property];
                            JSONModelError* dataErr = [JSONModelError errorInvalidDataWithMessage:msg];
                            *err = [dataErr errorByPrependingKeyPathComponent:property.name];
                        }
                        return NO;
                    }
                }

                // 3.1) handle matching standard JSON types
                //3.1)句柄匹配标准JSON类型
                //对象类型
                if (property.isStandardJSONType && [jsonValue isKindOfClass: property.type]) {

                    //mutable properties
                    //可变类型的属性
                    if (property.isMutable) {
                        jsonValue = [jsonValue mutableCopy];
                    }

                    //set the property value
                    //为属性赋值
                    if (![jsonValue isEqual:[self valueForKey:property.name]]) {
                        [self setValue:jsonValue forKey: property.name];
                    }
                    continue;
                }

                // 3.3) handle values to transform
                //3.3)处理要转换的值
                //当前的值的类型与对应的属性的类型不一样的时候,需要查看用户是否自定义了转换器(例如从NSSet到NSArray转换:-(NSSet *)NSSetFromNSArray:(NSArray *)array)
                if (
                    (![jsonValue isKindOfClass:property.type] && !isNull(jsonValue))
                    ||
                    //the property is mutable
                    //属性是可变的
                    property.isMutable
                    ||
                    //custom struct property
                    //自定义结构属性
                    property.structName
                    ) {

                    // searched around the web how to do this better
                    // but did not find any solution, maybe that's the best idea? (hardly)
                    //在网上搜索如何更好地做到这一点
                    //但是没有找到任何解决方案,也许这是最好的主意?(几乎没有)
                    Class sourceClass = [JSONValueTransformer classByResolvingClusterClasses:[jsonValue class]];

                    //JMLog(@"to type: [%@] from type: [%@] transformer: [%@]", p.type, sourceClass, selectorName);

                    //build a method selector for the property and json object classes
                    //为属性和json对象类构建方法选择器
                    NSString* selectorName = [NSString stringWithFormat:@"%@From%@:",
                                              (property.structName? property.structName : property.type), //target name目标名
                                              sourceClass]; //source name源名称
                    SEL selector = NSSelectorFromString(selectorName);

                    //check for custom transformer
                    //查看自定义的转换器是否存在
                    BOOL foundCustomTransformer = NO;
                    if ([valueTransformer respondsToSelector:selector]) {
                        foundCustomTransformer = YES;
                    } else {
                        //try for hidden custom transformer
                        //尝试隐藏自定义转换器
                        selectorName = [NSString stringWithFormat:@"__%@",selectorName];
                        selector = NSSelectorFromString(selectorName);
                        if ([valueTransformer respondsToSelector:selector]) {
                            foundCustomTransformer = YES;
                        }
                    }

                    //check if there's a transformer with that name
                    //检查是否有同名变压器
                    //如果存在自定义转换器,则进行转换
                    if (foundCustomTransformer) {
                        IMP imp = [valueTransformer methodForSelector:selector];
                        id (*func)(id, SEL, id) = (void *)imp;
                        jsonValue = func(valueTransformer, selector, jsonValue);

                        if (![jsonValue isEqual:[self valueForKey:property.name]])
                            [self setValue:jsonValue forKey:property.name];
                    } else {
                        //如果没有自定义转换器,返回错误
                        if (err) {
                            NSString* msg = [NSString stringWithFormat:@"%@ type not supported for %@.%@", property.type, [self class], property.name];
                            JSONModelError* dataErr = [JSONModelError errorInvalidDataWithTypeMismatch:msg];
                            *err = [dataErr errorByPrependingKeyPathComponent:property.name];
                        }
                        return NO;
                    }
                } else {
                    // 3.4) handle "all other" cases (if any)
                    //3.4)处理“所有其他”情况(如有)
                    if (![jsonValue isEqual:[self valueForKey:property.name]])
                        [self setValue:jsonValue forKey:property.name];
                }
            }
        }
    }

    return YES;
}
  • 首先遍历模型类中的所有属性
for (JSONModelClassProperty* property in [self __properties__])
  • 找到JSON字典中与模型类对应的key
        NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
  • 获取keyvalue,这里不用担心获取为空,因为在上一步验证dict是否与keymapper有匹配的方法中已经验证过了
id jsonValue;
        @try {
            jsonValue = [dict valueForKeyPath: jsonKeyPath];
        }
        @catch (NSException *exception) {
            jsonValue = dict[jsonKeyPath];
        }
  • 进行一系列检查操作,就不讲了,最后终于通过KVC将JSON字典中的值赋给了模型类
            if (property.type == nil && property.structName==nil) {

                //generic setter
                //通用setter
                //kvc赋值
                if (jsonValue != [self valueForKey:property.name]) {
                    [self setValue:jsonValue forKey: property.name];
                }

                //skip directly to the next key
                //直接跳到下一个键
                continue;
            }

总结

JSONModel提供了一种方法,

  • 得到的JSON数据自动与Model进行匹配
  • 还提供了keyMapper将JSON键自动映射到模型的属性
  • 还让我们可以自定义错误处理

这里再讲一下JSONModel的实现流程:

方法中通过获取JSONModel类的属性列表,与传入的JSON数据自动匹配,同时还可以通过KeyMapper修改不相同的映射,如果模型类与JSON数据字段不匹配则会抛出错误(这里体现为Model中某些必须的属性没有在JSON数据中找到相应的映射),最后如果类型等都检查成功,则通过KVC将JSON数据中的value设置在Model类的对应的属性上

if (jsonValue != [self valueForKey:property.name]) {
                    [self setValue:jsonValue forKey: property.name];

这里再总结一下JSONModel的几个优点

  • 首先就是Runtime中动态解析Model数据类型,可以实现自动匹配
  • 然后在setup方法中还通过AssociatedObject实现了已经解析过了Model的属性列表等必须信息的缓存,避免了相同模型的重复解析
  • 还有就是KVC附值
  • 使用KeyMapper修改不一致的JSON字段与属性

这里还有必要再讲一下KeyMapper,你可以通过重写模型类中的 +keyMapper 方法来自定义键映射。这个方法需要返回一个 JSONKeyMapper 对象,该对象通过初始化方法接收一个字典来定义映射关系。

@interface User : JSONModel
@property (strong, nonatomic) NSString *userId;
@property (strong, nonatomic) NSString *email;
@property (strong, nonatomic) NSString *userDescription;
@end

@implementation User

+ (JSONKeyMapper *)keyMapper {
    return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
        @"userId": @"id",
        @"email": @"email_address",
        @"userDescription": @"description"
    }];
}

@end

参考博客:
JSONModel源码解析
【iOS-JSONModel源码】
JSONModel源代码解析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/694789.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小白都可以通过U盘重装系统,再也不用花50块钱去安装系统啦

下载Ventoy 软件 1、今天带着大家通过Ventoy 安装Windows 11 系统。 2、首先我们通过官网如下地址&#xff1a;https://www.ventoy.net/cn/&#xff0c;找到我们对应系统的Ventoy 软件安装包。 3、通过官网可以找到软件包的地址地址&#xff0c;如下图所示。 4、如下就是我下…

面试题 17.15. 最长单词

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public: struct Trie {Trie() {end false;next.resize(26, nullptr);}bool end;std::vector<Trie*> next; };void insert_trie(Trie* root, std::string& word) {Trie* cur root;for (int i 0; i <…

《MySQL是怎样运行的》读书笔记(三) B+树索引

前言 从前面数据存储结构中我们已经知道了页和记录的关系示意图: 其中页a、页b、页c ... 页n 这些页可以不在物理结构上相连&#xff0c;只要通过双向链表相关联即可。 在正式介绍索引之前&#xff0c;我们需要了解一下没有索引的时候是怎么查找记录的。下边先只讨论搜索条件…

SpringBoot 参数验证的几种方式

文章目录 SpringBoot 参数验证1、为什么要进行参数验证2、验证方式2.1 if 语句判断2.2 Assert2.3 Validator2.3.1 引入依赖2.3.2 定义参数实体类2.3.4 定义特定异常全局拦截方法2.3.5 定义校验类进行测试2.3.6 测试 2.4 自定义验证注解2.4.1 定义自定义注解2.4.2 定义自定义验证…

C#操作MySQL从入门到精通(20)——更新数据

前言: 谈到数据库,大家最容易脱口而出的就是增删改查,本文所说的更新数据就是增删改查的改,改变数据的意思。 本文测试使用的数据库如下: 1、更新一列 所谓更新一列的意思就是只更改一列数据,并且通常要使用where条件,因为不加这个条件的话会导致将所有行的数据进行…

Java | Leetcode Java题解之第137题只出现一次的数字II

题目&#xff1a; 题解&#xff1a; class Solution {public int singleNumber(int[] nums) {int a 0, b 0;for (int num : nums) {b ~a & (b ^ num);a ~b & (a ^ num);}return b;} }

十大人工智能企业

​​​​​​链接&#xff1a;​​​​​​人工智能品牌排行-人工智能十大公司-人工智能十大品牌-Maigoo品牌榜

Linux--进程间通信(system V共享内存)

目录 1.原理部分 2.系统调用接口 参数说明 返回值 1. 函数原型 2. 参数说明 3. 返回值 4. 原理 5. 注意事项 3.使用一下shmget&#xff08;一段代码&#xff09; 4.一个案例&#xff08;一段代码) 1.简单封装一下 2.使用共享内存 2.1挂接&#xff08;shmat&#x…

2024 年适用于 Linux 的 5 个微软 Word 替代品

对于那些最近由于隐私问题或其他原因而转向 Linux 的用户来说&#xff0c;可能很难替换他们最喜欢的、不在 Linux 操作系统上运行的应用程序。 寻找流行程序的合适替代品可能会成为一项挑战&#xff0c;而且并不是每个人都准备好花费大量时间来尝试弄清楚什么可以与他们在 Win…

新买的移动硬盘无法识别

文章目录 背景解决方案 背景 同事新买的移动硬盘&#xff0c;插在电脑上识别不出来盘符&#xff0c;检查了一下&#xff0c;硬盘没问题应该&#xff0c;是ssk的硬盘盒M.2的SSD&#xff0c;硬盘驱动也是正常的&#xff0c;插拔了几次&#xff0c;都不识别&#xff0c;换了太电脑…

fl studio怎么设置中文及 2024年最新fl studio选购指南

FL Studio让你的计算机就像是全功能的录音室&#xff0c;漂亮的大混音盘&#xff0c;先进的创作工具&#xff0c;让你的音乐突破想象力的限制。zol提供FL Studio中文版下载。 FL Studio中文版下载软件简介 FL Studio 让你的计算机就像是全功能的录音室&#xff0c;漂亮的大混…

基于实验的电动汽车动力电池SOC

前言 本文为笔者在学习《基于MATLAB的新能源汽车仿真》过程中学习笔记&#xff0c;所涉及的表格数据和公式均为书籍里的。仿真数据是网上找的恒电流放电数据。本文仅作为笔者的入门学习记录。 一、分析动力电池SOC估算方法 SOC是指动力电池按照规定放电条件可以释放的容量占…

java版知识付费saas租户平台:剖析现代知识付费平台的功能架构与运营逻辑

在数字化学习的时代背景下&#xff0c;知识付费平台已经成为教育行业的一颗璀璨明星&#xff0c;以其用户需求为中心&#xff0c;提供便捷高效的学习途径。这些平台汇聚了众多专业知识&#xff0c;覆盖职业技能、生活兴趣和人文社科等多个领域&#xff0c;满足不同用户的学习需…

安全专业的硬件远控方案 设备无网也能远程运维

在很多行业中&#xff0c;企业的运维工作不仅仅局限在可以联网的IT设备&#xff0c;不能连接外网的特种设备也需要专业的远程运维手段。 这种特种设备在能源、医疗等行业尤其常见&#xff0c;那么我们究竟如何通过远程控制&#xff0c;对这些无网设备实施远程运维&#xff0c;…

[Algorithm][动态规划][01背包问题][目标和][最后一块石头的重量Ⅱ]详细讲解

目录 1.目标和1.题目链接2.算法原理详解3.代码实现 2.最后一块石头的重量 II1.题目链接2.算法原理详解3.代码实现 1.目标和 1.题目链接 目标和 2.算法原理详解 问题转化&#xff1a;在数组中选择一些数&#xff0c;让这些数的和等于a&#xff0c;一共有多少种选法&#xff1f…

推荐4个好用有趣的软件

MyComic——漫画聚合软件 MyComic是一款界面简洁、分类详尽的漫画阅读软件&#xff0c;专为动漫爱好者设计。它提供了丰富的高清漫画资源&#xff0c;支持在线免费阅读&#xff0c;并且可以一键下载到书架&#xff0c;方便随时离线观看&#xff0c;节省流量。用户可以轻松找到喜…

CSAPP Lab01——Data Lab完成思路

陪你把想念的酸拥抱成温暖 陪你把彷徨写出情节来 未来多漫长再漫长还有期待 陪伴你 一直到 故事给说完 ——陪你度过漫长岁月 完整代码见&#xff1a;CSAPP/datalab-handout at main SnowLegend-star/CSAPP (github.com) 01 bitXor 这道题是用~和&计算x^y。 异或是两个…

Android Compose 十:常用组件列表 监听

1 去掉超出滑动区域时的拖拽的阴影 即 overScrollMode 代码如下 CompositionLocalProvider(LocalOverscrollConfiguration provides null) {LazyColumn() {items(list, key {list.indexOf(it)}){Row(Modifier.animateItemPlacement(tween(durationMillis 250))) {Text(text…

接口(API)开发,测试工具-apifox

前言 为什么需要接口&#xff08;API&#xff09;? 因为不同的平台或系统可能使用不同的技术栈、编程语言或数据格式。API提供了一个标准化的方式&#xff0c;使得这些不同的系统可以相互交换数据和功能调用&#xff0c;实现互操作性 在开发日常的项目交互中&#xff0c;不…

英伟达的GPU(4)

更第四篇&#xff0c;上周有点私事&#xff0c;恢复更新 上次的文章 英伟达的GPU(3) (qq.com) 书接前文&#xff0c;我们上章说要更新GPU的内存机制&#xff0c;本次就讲点这个 先做个定义&#xff0c;我们说内存&#xff08;显存&#xff09;&#xff0c;也分物理内存&#…