【iOS】JSONModel源码学习

发布于:2025-06-10 ⋅ 阅读:(21) ⋅ 点赞:(0)

JSONModel源码解析

前言

笔者学习一下有关于JSONModel的源码的内容,来了解它底层的一个实现过程

源码核心 initWithDictionary:error:

这里我们从JSONModel这个第三方库的一个核心开始介绍:

-(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;
}
  • 前四个步骤都是对于错误的一个处理
  • 方法5,这里真正的mapping
  • 方法6是自定义给用户自己定义错误的方法,如果复合了用户自己定义的错误
  • 方法7成功返回模型对象

这里我们先了解一下JSONModel所持有的一些数据:

  • 关联对象kClassPropertiesKey:(用来保存所有属性信息的NSDictionary)
{
       age = "@property primitive age (Setters = [])";
      name = "@property NSString* name (Standard JSON type, Setters = [])";
    gender = "@property NSString* gender (Standard JSON type, Setters = [])";
}
  • 关联对象kClassRequiredPropertyNamesKey:(用来保存所有属性的名称NSSet)
{(
    name,
    age,
    gender
)}
  • 关联对象kMapperObjectKey,用来保存JSONKeyMapper:自定义的mapper,具体的使用方法在上面的例子中可以看到
  • JSONModelClassProperty分支JSONModel的一个属性,它包含了对应属性的名字:(name:gender).类型(type:NSString),是否是JSONModel支持的一个类型

首先,在这个模型类的对象被初始化的时候,遍历自身到所有的父类(直到JSONModel为止),获取所有的属性,并将其保存在一个字典里。获取传入字典的所有key,将这些key与保存的所有属性进行匹配。如果匹配成功,则进行kvc赋值。

load方法里,定义了该框架支持的类型:

+(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));
        }
    });
}

这里我们看一下方法3的init方法,作者都做了什么:

-(id)init
{
    self = [super init];
    if (self) {
        [self __setup__];
    }
    return self;
}
-(void)__setup__
{
    //只有第一次实例化时,才执行
    if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {
        [self __inspectProperties];
    }
    
    //如果存在自定义的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
                                 );
    }
}

这里我们看一下上面第一次实例化的时候所执行的核心函数:

__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];
                     
            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
                             );
}

这里需要注意以下几个点:

  1. 作者while函数,获取当前类和当前类的除了JSONModel的所有父类的属性保存到一个字典中,在将来用于和传入的字典进行一个映射
  2. 这里用JSONModelClassProperty类封装了JSONModel的每一个属性,这个类有两个重要的属性一个是name.另一个是type,这个是属性的类型
  3. 作者将属性分成类下面几个类型:
  • 对象(不含有协议)
  • 对象(含有协议,属于模型嵌套)
  • 基本数据类型
  • 结构体

在最后将这个属性字典通过关联对象绑定到我们的类上面.

这时候我们再看上面的initWithDictionary的第四个步骤:

 //方法4. 检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO)
    if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
        return nil;
    }

这里我们在进入__doesDictionary这个方法里面看一下它做了什么事情?

__doesDictionary
-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{
    //check if all required properties are present
  //获得字典里面的所有key
    NSArray* incomingKeysArray = [dict allKeys];
  //返回保存所有属性名称的数组(name, age, gender)
    NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;
  //从array中拿到set
    NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];
	//如果用户自定义了mapper,则进行转化
    //transform the key names, if necessary
    if (keyMapper || globalKeyMapper) {

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

        //loop over the required properties list
      	// 遍历所有需要被转化的属性列表
        for (JSONModelClassProperty* property in [self __properties__]) {

            transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;

            //check if exists and if so, add to incoming keys
            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;
    }
		// 查看当前model的属性集合是否大于传入的属性集合.如果是则返回错误
  //也就是说模型类里的属性使不能多与传入字典的key的
    //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;
    }
		// 不需要了释放掉
    //not needed anymore
    incomingKeys= nil;
    requiredProperties= nil;

    return YES;
}

这里需要注意的:

  • model类里面定义的属性集合是不能大于传入的字典的key集合
  • 如果存在了用户自定义的mapper,则需要按照用户的定义来进行转化

现在看上面的方法5,也就是当前真正从字典里面获取值并且赋值给当前模型对象的实现:

__importDictionary
-(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
{
    //遍历保存的所有属性的字典
    for (JSONModelClassProperty* property in [self __properties__]) {
        //将属性的名称拿过来,作为key,用这个key来查找传进来的字典里对应的值
        NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;
        //用来保存从字典里获取的值
        id jsonValue;        
        @try {
            jsonValue = [dict valueForKeyPath: jsonKeyPath];
        }
        @catch (NSException *exception) {
            jsonValue = dict[jsonKeyPath];
        }
        //字典不存在对应的key
        if (isNull(jsonValue)) {
            //如果这个key是可以不存在的
            if (property.isOptional || !validation) continue;            
            //如果这个key是必须有的,则返回错误
            if (err) {
                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;
        }
        //如果是兼容的类型:
        if (property) {
            // 查看是否有自定义setter,并设置
            if ([self __customSetValue:jsonValue forProperty:property]) {
                continue;
            };
            // 基本类型
            if (property.type == nil && property.structName==nil) {
                //kvc赋值
                if (jsonValue != [self valueForKey:property.name]) {
                    [self setValue:jsonValue forKey: property.name];
                }
                continue;
            }
            // 如果传来的值是空,即使当前的属性对应的值不是空,也要将空值赋给它
            if (isNull(jsonValue)) {
                if ([self valueForKey:property.name] != nil) {
                    [self setValue:nil forKey: property.name];
                }
                continue;
            }
            // 1. 属性本身是否是jsonmodel类型
            if ([self __isJSONModelSubClass:property.type]) {
                //通过自身的转模型方法,获取对应的值
                JSONModelError* initErr = nil;
                id value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];
                if (!value) {               
                    //如果该属性不是必须的,则略过
                    if (property.isOptional || !validation) continue;
                    //如果该属性是必须的,则返回错误
                    if((err != nil) && (initErr != nil))
                    {
                        *err = [initErr errorByPrependingKeyPathComponent:property.name];
                    }
                    return NO;
                }            
                //当前的属性值为空,则赋值
                if (![value isEqual:[self valueForKey:property.name]]) {
                    [self setValue:value forKey: property.name];
                }
                continue;
            } else {
                // 如果不是jsonmodel的类型,则可能是一些普通的类型:NSArray,NSString。。。
                // 是否是模型嵌套(带有协议)
                if (property.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;
                    }
                }
                // 对象类型
                if (property.isStandardJSONType && [jsonValue isKindOfClass: property.type]) {
                    //可变类型
                    if (property.isMutable) {
                        jsonValue = [jsonValue mutableCopy];
                    }
                    //赋值
                    if (![jsonValue isEqual:[self valueForKey:property.name]]) {
                        [self setValue:jsonValue forKey: property.name];
                    }
                    continue;
                }
                // 当前的值的类型与对应的属性的类型不一样的时候,需要查看用户是否自定义了转换器(例如从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
                    NSString* selectorName = [NSString stringWithFormat:@"%@From%@:",
                                              (property.structName? property.structName : property.type), //target name
                                              sourceClass]; //source name
                    SEL selector = NSSelectorFromString(selectorName);
                    //查看自定义的转换器是否存在
                    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;
                        }
                    }
                    //如果存在自定义转换器,则进行转换
                    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 {                       
                        //没有自定义转换器,返回错误
                        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)
                    if (![jsonValue isEqual:[self valueForKey:property.name]])
                        [self setValue:jsonValue forKey:property.name]; // 通过KVC赋值
                }
            }
        }
    }
    return YES;
}

其实这里也就分成以下四个步骤:

  • 首先遍历模型类中的所有属性
  • 找到JSON字典中与模型类对应的key
  • 获取jsonValue
  • 进行一系列的检查操作,最后通过KVC来将JSON字典中的值赋值给了模型类

小结

JSONModel实现了将JSON数据转化成Model数据:

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

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

  • 通过Runtime进行一个动态解析Model的数据
  • 然后在setup方法中通过关联对象缓存已经解析过的数据,减少类解析的流程
  • 通过KVC赋值修改不一致的JSON子段和属性