【iOS】—— JSONModel

JSONModel源码

      • [1. JSONModel介绍](#1. JSONModel介绍)
      • [2. JSONModel的其他用法](#2. JSONModel的其他用法)
        • [2.1 转换属性名称](#2.1 转换属性名称)
        • [2.2 自定义错误](#2.2 自定义错误)
      • [3. 源码分析](#3. 源码分析)
        • [3.1 - (id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err](#3.1 - (id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err)
        • [3.2 JSONModel持有的数据](#3.2 JSONModel持有的数据)
        • [3.3 load](#3.3 load)
        • [3.4 JSONModel的init方法](#3.4 JSONModel的init方法)
        • [3.5 __inspectProperties方法](#3.5 __inspectProperties方法)
        • [3.6 JSONModelClassProperty](#3.6 JSONModelClassProperty)
        • [3.7 __doesDictionary方法](#3.7 __doesDictionary方法)
        • [3.8 __importDictionary](#3.8 __importDictionary)
        • [3.9 到底什么是JSONKeyMapper?](#3.9 到底什么是JSONKeyMapper?)

1. JSONModel介绍

JSONModel是一个在iOS应用程序中使用的开源库,主要功能是JSON数据映射到Objective-C对象上。使用JSONModel可以方便地处理服务器返回的JSON格式的数据,将其转化为Objective-C对象,使得数据访问变的更加简单。

JSONModel提供了一种间接的方式来定义数据模型,开发者只需要创建一个继承自JSONModel的类,并在其中定义属性,将可以将JSON数据映射到对象上。JSONModel之间的嵌套关系可以通过嵌套JSONModel子类来实现。

总之,JSONModel是一个强大的数据映射工具,可以帮助开发者处理JSON数据,提高iOS开发的效率。

2. JSONModel的其他用法

2.1 转换属性名称

JSON的传入,在定义JSONModel数据模型的时候已经准备好某个数据名称,由于某些因素导致传入的JSON数据名称不符,就使用转换属性名称方法。

如下字典的key从url传入的时候变成了imageURL,这会就可以使用这个方法避免数据传入错误

objective-c 复制代码
+ (JSONKeyMapper *)keyMapper {
    return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"url": @"ImgaeURL"}];
}

这样就可以避免因为JSON数据和Model里面定义的数据不同而发生数据NULL的错误。

2.2 自定义错误

JSONModel除了一些框架里自己处理的错误,框架的作者允许自己定义属于自己的框架。方便对JSON数据传入的时候,是否进行转换的决定。

objective-c 复制代码
 - (BOOL)validate:(NSError *__autoreleasing *)error {
    
}

3. 源码分析

看源码之前先上别人的流程图,配合着流程图看即可

上面的流程图,主要是下面这个方法:

objective-c 复制代码
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err

这个方法内部是通过许多方法嵌套来完成的。

3.1 - (id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
objective-c 复制代码
-(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;
}

在iOS开发中isKindOfClass和isMemberOfClass的区别

isKindOfClass: 判断对象是否当前类以及其子类的实例,如果是返回YES,不是返回NO。

isMemberOfClass: 判断对象是否为当前类的实例,如果是返回YES,不是返回NO。

总结上面6个方法:

  • 前4个方法,都是对错误的发现和处理。
  • 第五个方法是核心方法,判断模型映射。
  • 第六个方式是自定义错误判断,如果符合自定义的错误,就是mapping成功也会报错。
3.2 JSONModel持有的数据
objective-c 复制代码
#pragma mark - associated objects names
static const char * kMapperObjectKey;
static const char * kClassPropertiesKey;
static const char * kClassRequiredPropertyNamesKey;
static const char * kIndexPropertyNameKey;

关联对象kClassPropertiesKey: 保存数据所有属性信息的NSDictionary。

关联对象kClassRequiredPropertyNamesKey: 用来保存所有属性名称NSSet。

关联对象kMapperObjectKey: 用来保存JSONKeyMapper。

JSONModelClassProperty : 封装的JSONModel的一个属性,它包含了对应属性的名字 (name:sex),类型(type:NSString),是否是JSONModel支持的类型(isStandardJSONType:YES/NO),是否是可变对象(isMutable:YES/NO)等属性。

总结一下完成模型的映射:

在模型类对象被初始化的时候,遍历自身到所有父类,获取所有的属性,将属性保存到一个字典中。获取传入到字典的key,将这些key与属性匹配,如果匹配成功的话,则进行KVC赋值。

3.3 load

在 Objective-C 中,每个类都有一个load方法,这个方法在程序启动的时候,被自动调用。这个方法主要作用是在类被加载时执行的方法,执行一些初始化操作,常见的用法:注册通知,方法交换等。

objective-c 复制代码
 +(void)load
{
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        @autoreleasepool {           
            //兼容的对象属性
            allowedJSONTypes = @[
                [NSString class], [NSNumber class], [NSDecimalNumber class], [NSArray class], [NSDictionary class], [NSNull class], //immutable JSON classes
                [NSMutableString class], [NSMutableArray class], [NSMutableDictionary class] //mutable JSON classes
            ];
            //兼容的基本类型属性
            allowedPrimitiveTypes = @[
                @"BOOL", @"float", @"int", @"long", @"double", @"short",
                //and some famous aliases
                @"NSInteger", @"NSUInteger",
                @"Block"
            ];
            //转换器
            valueTransformer = [[JSONValueTransformer alloc] init];
            
            //自己的类型
            JSONModelClass = NSClassFromString(NSStringFromClass(self));
        }
    });
}

在JSONModel类中,load主要是进行初始化操作:

  • 初始化allowedJSONTypes属性:这个属性包含所有兼容的JSON类型的数组。
  • 初始化allowedPrimitiveType属性:这个属性包含所有兼容基本数据类型的数组。
  • 初始化valueTransformer属性:这个是一个 JSONValueTransformer 类的实例。
  • 初始化 JSONModelClass 属性,这个属性是一个指向当前类的指针。

他们会被所有JSONModel子类所继承和共享,在子类中初始化这些属性是不必要的,在父类中都已经实现。

每个类都有自己的load方法,在自己的load方法实现一些必要的属性的初始化。

load方法在运行时加载一个类时自动调用,不论类被实例化多少次,都只会调用一次。

当在OC程序启动时,运行时会自动加载每个类的信息,在加载一个类的时候,会先检查这个类是否实现load,如果实现了,就调用该方法。

注意:

  • 分类中的load方法不会覆盖原始类中的load方法,而会在原始类的load方法调用之后再去执行。
3.4 JSONModel的init方法

通过源码发现 load方法之后紧跟着init方法的实现。看看init实现了什么,而init方法里面调用了一个-(void)__setup__方法,这个方法大有研究

objective-c 复制代码
 -(id)init
{
    self = [super init];
    if (self) {
        //do initial class setup进行初始类设置
        [self __setup__];
    }
    return self;
}

-(void)__setup__
{
    //if first instance of this model, generate the property list
    //如果是此模型的第一个实例,请生成属性列表(只有第一次实例化的时候,才执行)
    if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {
        //该__inspectProperties方法是该第三方框架的核心方法之一:它的任务是保存了所有需要赋值的属性。用作在将来与传进来的字典进行映射
        [self __inspectProperties];
    }

    //if there's a custom key mapper, store it in the associated object
    //如果有自定义键映射器,请将其存储在关联的对象中(如果存在自定义的mapper,则将它保存在关联对象里面,key是kMapperObjectKey)
    id mapper = [[self class] keyMapper];
    if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
        objc_setAssociatedObject(
                                 self.class,
                                 &kMapperObjectKey,
                                 mapper,
                                 OBJC_ASSOCIATION_RETAIN // This is atomic这是原子的
                                 );
    }
}

__setup__方法中:

  1. if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey))的作用是检查当前类是否被解析过,如果没有解析过,就调用__inspectProperties方法解析类的属性信息并缓存。
  2. objc_getAssociatedObject 是一个用于获取关联对象的函数self.class是第一个参数,表示获取当前类的关联对象,如果关联对象不存在,就说明没有进行解析调用__inspectProperties方法进行解析。

关联对象的两个函数objc_getAssociatedObjectobjc_setAssociatedObject等函数用于关联对象的机制是 Objective-C 运行时提供的一种方法,**它可以在运行时为一个对象关联一些额外的数据,而不需要修改这个对象的定义。**这种机制在某些情况下非常方便,比如在 JSONModel 中就用它来缓存类的属性信息。

3.5 __inspectProperties方法

__inspectProperties方法该框架的核心方法:他的任务是保存所有需要赋值的属性,用做后来与传进来的字典进行映射。

__inspectProperties方法用于检查模型类的属性,并将其转化为一个NSDictionary对象。该方法检查模型类的属性,并将其转化为NSDictionary对象。该方法遍历模型类的属性,解析模型类的相关信息,存储在NSDictionary中。

该方法在运行时,为每一个属性创建JSONModelProperty对象,然后就这些对象保存到NSMultableDictionary中,以属性名为键,JSONModelProperty对象为值。最后NSMultableDictionary对象将会通过关联对象与模型类连接起来,方便访问。

objective-c 复制代码
 -(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
                             );
}

注意:

  • JSONModelClassProperty类封装了JSONModel的每一个属性。这个类有两个重要的属性:一个是name,它是属性的名称(例如gender)。另一个是type,它是属性的类型(例如NSString)。
  • 作者将属性分为了如下几个类型:
    (1) 对象(不含有协议)。
    (2) 对象(含有协议,属于模型嵌套)。
    (3) 基本数据类型。
    (4) 结构体。
3.6 JSONModelClassProperty

JSONModelClassProperty是上述过程比较重要的一环,JSONModel里的每一个属性,都被封装成一个JSONModelClassProperty对象,然后扫描property判断是否能够完成映射。

JSONModelClassProperty是JSONModel中用于描述一个属性的类。它包含了一个属性的各种信息,比如属性名、类型、元素类型(如果是数组类型的话)、转换器等。这个类成功的帮助JSONModel完成了许多冗杂的任务

JSONModelClassProperty定义了下列属性:

  • name:属性名
  • type:属性类型,用字符串表示,可以是基本类型,也可以是自定义类型。
  • isStandardJSONType:是否是标准的JSON类型。标准的JSON类型指的是:NSString、NSNumber、NSDecimalNumber、NSArray、NSDictionary和NSNull。
  • isMutable:属性是否可变。
  • isOptional:属性是否可选。
  • isArray:属性是否是数组类型。
  • elementType:如果属性是数组类型,这个属性指定了数组元素的类型。
  • customGetter:自定义的getter方法。
  • customSetter:自定义的setter方法。
  • getterType:getter方法返回值的类型。
  • setterType:setter方法参数的类型。
  • valueTransformer:值转换器。
3.7 __doesDictionary方法

__doesDictionary方法是JSONModel中的一个私有方法用于判断一个对象是否可以被转化为NSDictionary类型。如果可以,该方法会返回YES,否则返回NO

具体实现:通过OC的runtime机制获取对象的类的信息,判断是否实现了NSDictionary的allKey方法,如果实现了就说明对象可以被转化为NSDictionary类型,返回YES,否则返回NO。

objective-c 复制代码
 //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;
}

model类里面的属性集合是不能大于传入字典中的key的集合**(我们所写的接受数据的属性的数量需要小于等于传入的字典的key集合)**

就会到了最后的核心方法,因为方法6的自定义错误是选择性实现的,若是通过这个方法的判断那么我们就即将完成模型和字典的映射了。

3.8 __importDictionary

同样也是返回BOOL类型的方法,很重要的一步。

__importDictionary方法内部是将NSDictionary对象转化为JSONModel对象的方法。

JSONModel需要将NSDictionary对象转化为JSONModel对象,需要调用__importDictionary方法。该方法首先通过classProperty获取JSONModel的属性列表,遍历每一个属性,根据属性名称,在字典中查找对应的值,并将值设置到JSONModel对象的对应属性中。

objective-c 复制代码
//作者在最后给属性赋值的时候使用的是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;
}

在设置属性值时,__importDictionary方法还会进行一些类型转换和校验

  • 如果属性是JSONModel类型,则将字典转换为该类型的JSONModel对象,并设置到当前属性中。
    如果属性是基本数据类型(如int、float等),则将字典中的数值转换为相应的数据类型,并设置到当前属性中。
  • 如果属性是NSDate类型,则将字典中的时间戳转换为NSDate对象,并设置到当前属性中。
    总之,__importDictionary方法的主要作用是将NSDictionary对象转换为JSONModel对象,并进行一些类型转换和校验。
3.9 到底什么是JSONKeyMapper?

在使用 JSONModel 时,JSON 数据中的属性名通常与我们所使用的类的属性名并不完全相同。因此,JSONModel 提供了一个叫做 JSONKeyMapper 的工具类,用于在 JSON 数据中查找与类属性名相对应的属性名,以便进行正确的映射。

JSONKeyMapper 是一个可定制的映射器,它提供了两种映射方式:

  • 下划线式(UnderscoreCase)映射:将下划线形式的 JSON 数据中的属性名转换成类属性名(如:foo_bar -> fooBar)。
  • 驼峰式(CamelCase)映射:将驼峰形式的 JSON 数据中的属性名转换成类属性名(如:fooBar -> foo_bar)。
objective-c 复制代码
 JSONKeyMapper *mapper = [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
    @"propertyOne": @"property_one",
    @"propertyTwo": @"property_two"
}];
MyModel *model = [[MyModel alloc] initWithDictionary:jsonDict error:nil];

其实也就是为了方便我们前后端的数据类型发生改变的时候,添加一些自定义的属性映射方式避免不必要的错误。

除了开始说的重写+ (JSONKeyMapper *)keyMapper { return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"url": @"ImgaeURL"}]; }

也可以可以通过 JSONModel 中的 - (id)initWithDictionary:(NSDictionary *)dict error:(NSError **)err方法来传递一个自定义的 JSONKeyMapper实例。

相关推荐
a45763687626 分钟前
Objective-c protocol 练习
开发语言·macos·objective-c
二流小码农1 小时前
鸿蒙开发:DevEcoStudio中的代码提取
android·ios·harmonyos
BAGAE1 小时前
Flutter 与原生技术(Objective-C/Swift,java)的关系
java·开发语言·macos·objective-c·cocoa·智慧城市·hbase
RocketJ1 小时前
mac电脑.sh文件,用来清除git当前分支
git·elasticsearch·macos
Digitally7 小时前
如何用4 种可靠的方法更换 iPhone(2025 年指南)
ios·iphone
97650333511 小时前
iOS 审核 cocos 4.3a【苹果机审的“分层阈值”设计】
flutter·游戏·unity·ios
I烟雨云渊T11 小时前
iOS Alamofire库的使用
ios
程序员老刘·11 小时前
iOS 26 beta1 真机无法执行hot reload
flutter·ios·跨平台开发·客户端开发
EndingCoder11 小时前
React Native 构建与打包发布(iOS + Android)
android·react native·ios
程序员小刘12 小时前
HarmonyOS 5鸿蒙多端编译实战:从Android/iOS到HarmonyOS 5 的跨端迁移指南详
android·ios·华为·harmonyos