iOS ------ JSONModel源码

一,JSONModel的基本使用

1,基本使用方法

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

这四个方法都是 JSONModel 类中的初始化方法,用于创建并初始化 JSONModel 的实例对象。
使用情况:

  • initWithDictionary:error:
    将一个包含 JSON 对象的字典转换为 JSONModel 实例对象时使用。
  • initWithData:error:
    将从从网络获取到 JSON 数据转换为 JSONModel 实例对象时使用。
  • initWithString:error:
    将一个 JSON 字符串转换为 JSONModel 实例对象时使用。
  • initWithString:usingEncoding:error:
    当您已经有一个 JSON 字符串,并需要指定字符编码时使用。

这些方法提供了不同的输入方式,以适应不同的数据源和数据表示形式。

2,转换属性名称

有时我们获取的josn数据的key发生变化和定义的model对象的属性不一样,而模型属性不能轻易改变。比如原来字典里的gender这个key变成了sex,这就需要我们定义一个转换的mapper(JSONKeyMapper):

@implementation Person
+ (JSONKeyMapper *)keyMapper
{
    return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
                                                                  @"gender": @"sex",                                                             }];
}

具体效果:

NSDictionary *dict = @{
                        @"name":@"Jack",
                        @"age":@23,
                        @"sex":@"male",
                      };
NSError *error;
Person *person = [[Person alloc] initWithDictionary:dict error:&error];

输出:

 
   [name]: Jack
   [age]: 23
   [gender]: male

没有受到传入字典里key值的变化的影响。

3,自定义错误

除了一些框架自己处理的错误(比如传入的对象不是字典),框架的作者也允许我们自己定义属于我们自己的错误。
比如,当age对应的数值小于25的时候,打印出Too young!,并阻止模型的转换

在模型的实现文件中:

- (BOOL)validate:(NSError **)error
{
    if (![super validate:error])
        return NO;
    
    if (self.age < 25)
    {
        *error = [NSError errorWithDomain:@"Too young!" code:10 userInfo:nil];
        NSError *errorLog = *error;
        NSLog(@"%@",errorLog.domain);
        return NO;
    }
    
    return YES;
}

如果要被转化的数据age小于25,就会打印错误,并且模型也不会转化。

4,模型嵌套

有时,我们需要在模型里加一个数组,而这个数组里面的元素是另一个对象:这就涉及到了模型的嵌套。如果这样做我们需要在模型类中增加一个协议:

#import "JSONModel.h"
@protocol Friend;
@interface Friend : JSONModel
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
@interface Person : JSONModel
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *gender;
@property (nonatomic, strong) NSArray *friends;//数组,嵌套模型
@end

而且要在Person的实现文件里加上这一段代码:

@implementation Friend
@end

不想因为服务器的某个值没有返回(nil)就使程序崩溃, 我们会加关键字Optional

@property (nonatomic, copy) NSString<optional> *name;

如果不想每一条属性都添加,我们也可以在.m文件中重写方法

+ (BOOL) propertyIsOptional:(NSString *)propertyName {
    return YES;
}

二,源码分析

1,jsonModel的四个基本的使用方法的源码

-(id)initWithString:(NSString*)string error:(JSONModelError**)err
{
    JSONModelError* initError = nil;
    id objModel = [self initWithString:string usingEncoding:NSUTF8StringEncoding error:&initError];
    if (initError && err) *err = initError;
    return objModel;
}
-(id)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError**)err
{
    //check for nil input
    if (!string) {
        if (err) *err = [JSONModelError errorInputIsNil];
        return nil;
    }

    JSONModelError* initError = nil;
    id objModel = [self initWithData:[string dataUsingEncoding:encoding] error:&initError];
    if (initError && err) *err = initError;
    return objModel;

}
-(instancetype)initWithData:(NSData *)data error:(NSError *__autoreleasing *)err
{
    //check for nil input
    if (!data) {
        if (err) *err = [JSONModelError errorInputIsNil];
        return nil;
    }
    //read the json
    JSONModelError* initError = nil;
    id obj = [NSJSONSerialization JSONObjectWithData:data
                                             options:kNilOptions
                                               error:&initError];

    if (initError) {
        if (err) *err = [JSONModelError errorBadJSON];
        return nil;
    }

    //init with dictionary
    id objModel = [self initWithDictionary:obj error:&initError];
    if (initError && err) *err = initError;
    return objModel;
}
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{
    //check for nil input
    if (!dict) {
        if (err) *err = [JSONModelError errorInputIsNil];
        return nil;
    }

    //invalid input, just create empty instance
    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;
    }

    //create a class instance
    self = [self init];
    if (!self) {

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

    //check incoming data structure
    if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
        return nil;
    }

    //import the data from a dictionary
    if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
        return nil;
    }

    //run any custom model validation
    if (![self validate:err]) {
        return nil;
    }

    //model is valid! yay!
    return self;
}

这些方法从上到下依次实现嵌套,最终都实现了

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

2,具体看一看initWithDictionary:error:

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

这个方法的大致流程如下:

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

3,JSONModel所持有的一些关联对象名称:

#pragma mark - associated objects names
static const char * kMapperObjectKey;
static const char * kClassPropertiesKey;
static const char * kClassRequiredPropertyNamesKey;
static const char * kIndexPropertyNameKey;

这些关联对象名称的作用如下:

  1. kMapperObjectKey:与JSON映射器相关联的对象的关联对象键。在这种情况下,它可能用于将JSON映射器对象与类或实例关联起来。JSON映射器通常用于将JSON数据映射到模型对象的属性上。

  2. kClassPropertiesKey:与类属性相关联的对象的关联对象键。在这个上下文中,它可能用于将类属性的信息或描述与类对象关联起来。这些信息可能包括属性名称、类型、访问权限等。

  3. kClassRequiredPropertyNamesKey:与类的必需属性名称相关联的对象的关联对象键。在这个上下文中,它可能用于将类的必需属性名称与类对象关联起来。必需属性是指在模型对象中必须存在的属性。

  4. kIndexPropertyNameKey:与索引属性名称相关联的对象的关联对象键。在这个上下文中,它可能用于将索引属性名称与类对象关联起来。索引属性通常用于在模型对象集合中标识唯一的对象。

这些关联对象名称的作用是在运行时为类或实例关联附加的元数据或相关信息。通过使用关联对象,可以将自定义的数据与类或实例相关联,以提供额外的功能或元数据,例如属性映射信息、必需属性列表等。这样可以在运行时动态地访问和操作这些附加的信息。

具体的流程:

4,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));
        }
    });
}
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
                                 );
    }
}
-(JSONKeyMapper*)__keyMapper
{
    //get the model key mapper
    return objc_getAssociatedObject(self.class, &kMapperObjectKey);
}
init中的__inspectProperties方法
-(void)__inspectProperties
{
//    最终保存所有属性的字典,形式为:
//    {
//        age = "@property primitive age (Setters = [])";
//        friends = "@property NSArray*<Friend> 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<Friend>",&,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的所有父类的属性,并将这些属性保存到一个字典中。在将来用于和传入的字典进行映射
该字典的格式如下:

{
   propertyName1 = "@property propertyType1 (propertyAttributes1)";
   propertyName2 = "@property propertyType2 (propertyAttributes2)";
   ...
}

JSONModelClassProperty类封装了JSONModel的每一个属性,每个属性都以属性名称作为键,以属性的类型和属性特性作为值进行保存。属性的类型和特性信息是通过扫描属性的属性字符串来获取的。

5,__doesDictionary:matchModelWithKeyMapper:error:方法

-(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
{
    //拿到字典里所有的key
    NSArray* incomingKeysArray = [dict allKeys];

    //返回保存所有属性名称的数组(name,age,gender...)
    NSMutableSet* requiredProperties = [self __requiredPropertyNames].mutableCopy;

    //从array拿到set
    NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];

    //如果用户自定义了mapper,则进行转换
    if (keyMapper || globalKeyMapper) {

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

        //便利需要转换的属性列表
        for (JSONModelClassProperty* property in [self __properties__]) {

            //被转换成的属性名称 gender(模型内) -> sex(字典内)
            transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper] : property.name;

            //拿到sex以后,查看传入的字典里是否有sex对应的值
            id value;
            @try {
                value = [dict valueForKeyPath:transformedName];
            }
            @catch (NSException *exception) {
                value = dict[transformedName];
            }
            //如果值存在,则将sex添加到传入的keys数组中
            if (value) {
                [transformedIncomingKeys addObject: property.name];
            }
        }

        incomingKeys = transformedIncomingKeys;
    }

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

        //获取多出来的属性
        [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;
    }

    //不需要了,释放掉
    incomingKeys= nil;
    requiredProperties= nil;

    return YES;
}

这段代码是一个方法的实现,

  1. 其作用是检查传入的字典(NSDictionary)是否与当前模型类的属性匹配。
  2. 首先,它获取传入字典的所有键(keys)并保存在incomingKeysArray数组中。
  3. 然后,它获取当前模型类的必需属性名称并保存在requiredProperties的可变集合中。
  4. 接下来,它将incomingKeysArray数组转换为集合类型的incomingKeys
  5. 如果存在自定义的键映射器(keyMapper)或全局键映射器(globalKeyMapper),则使用键映射器将属性名称进行转换。它遍历模型类的属性列表,将每个属性的名称根据键映射器进行转换,然后检查传入字典中是否存在转换后的键对应的值。如果存在值,则将原始属性名称添加到transformedIncomingKeys集合中。
  6. 最后,它将transformedIncomingKeys集合赋值给incomingKeys,并检查当前模型类的必需属性是否都存在于incomingKeys集合中。如果有必需属性缺失,则记录错误信息,并返回NO。如果所有必需属性都存在于incomingKeys集合中,则返回YES表示匹配成功。

总之,这段代码用于验证传入字典与当前模型类的属性之间的匹配关系,并返回匹配结果

如果存在了用户自定义的mapper,则需要按照用户的定义来进行转换。(在这里是将gender转换为了sex)。

6,__importDictionary:withKeyMapper:validation:error:方法

-(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];
                }
            }
        }
    }

    return YES;
}

这个方法作用是将传入的NSDictionary对象(dict)中的数据映射到当前对象(self)的属性上,并进行验证和转换。

具体功能如下:

  1. 遍历当前对象的所有属性(通过[self properties]获取属性列表)。
  2. 获取属性的名称,并根据keyMapper映射规则将其转换为对应的JSON键路径(jsonKeyPath)。
  3. 从传入的字典(dict)中根据键路径(jsonKeyPath)获取对应的值(jsonValue)。
  4. 如果值(jsonValue)为null或字典中不存在对应的键,则根据属性的可选性和验证选项判断是否继续。
  • 如果属性是可选的或者验证选项为false,则继续下一个属性的处理。
  • 如果属性是必需的且验证选项为true,则返回错误信息。
  1. 检查获取到的值(jsonValue)的类型是否兼容当前属性的类型。
  • 如果值的类型不兼容,则返回错误信息。
  1. 对于兼容的类型:
  • 如果属性存在自定义的setter方法,则调用该方法设置值,并继续下一个属性的处理。
  • 对于基本类型的属性,使用KVC赋值。
  • 如果传来的值为空,即使属性的值不为空,也将空值赋给属性。
  • 如果属性是JSONModel类型,通过属性的转换方法初始化一个新的对象(value),并将其赋给属性。
  • 如果属性是普通的对象类型(如NSArray、NSString等),根据属性的协议进行转换,并赋值给属性。
  • 如果属性的值类型与属性类型不一致,检查是否存在自定义的转换器,并进行转换。
  • 如果以上情况都不满足,则直接赋值给属性。
  1. 处理完所有属性后,返回YES表示映射和转换成功。

这段代码的作用是将传入的字典数据映射到当前对象的属性上,并进行验证和类型转换,以实现属性与字典数据的对应关系。

三,总结

1,JSONModel的优点:

  1. 简化的数据映射:JSONModel提供了简单而直观的方式来将JSON数据映射到Objective-C对象的属性上。开发人员只需定义模型类和属性映射关系,就能轻松地进行数据映射,而无需手动解析和转换JSON数据。
  2. 自动类型转换:JSONModel能够自动将JSON数据的值转换为合适的Objective-C类型。它支持将JSON数据转换为各种常见的数据类型,如NSString、NSNumber、NSDate等,使开发人员无需手动处理类型转换的细节。
  3. 数据验证:JSONModel允许开发人员在模型类中定义数据验证规则。通过实现验证方法,可以对属性进行验证,并在验证失败时提供错误信息。这有助于确保从JSON数据中获取的值符合预期,并提高数据的完整性和可靠性。
  4. 嵌套对象支持:JSONModel支持嵌套的数据结构,可以将一个JSONModel对象作为另一个JSONModel对象的属性。这使得处理复杂的嵌套JSON数据变得简单而直观,可以轻松地创建层次结构的对象模型。
  5. 灵活的属性映射:JSONModel允许开发人员通过自定义属性映射规则来灵活处理属性与JSON键之间的映射关系。可以使用自定义的映射规则,使属性名和JSON键名之间存在不一致,从而适应不同的数据命名约定。
  6. 运行时特性支持:JSONModel使用运行时特性来动态处理属性。它使用关联对象(Associated Objects)来附加和获取额外的属性。这样可以在运行时动态地管理和访问属性,而无需修改原始类的定义。
  7. 扩展性和定制性:JSONModel提供了丰富的扩展点和定制选项,允许开发人员根据自己的需求进行定制和扩展。可以通过自定义转换器、忽略属性、替代属性名等方式来定制数据映射的行为,以满足各种复杂的数据处理需求。

2,JSONModel源码的具体流程图

在这里插入图片描述

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

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

相关文章

Linux网络-部署YUM仓库及NFS共享服务

目录 一.YUM仓库服务 1.YUM概述 1.1.YUM&#xff08;Yellow dog Updater Modified&#xff09; 2.准备安装源 2.1.软件仓库的提供方式 2.2.RPM软件包的来源 2.3.构建CentOS 7 软件仓库 2.4.在软件仓库中加入非官方RPM包组 3.一键安装软件包的工具&#xff1a; 好处&a…

申请Sectigo证书流程详解

Sectigo&#xff08;前身为Comodo CA&#xff09;&#xff0c;是目前主流SSL证书的一种&#xff0c;目前全球范围内应用度也非常广泛&#xff0c;是目前众多品牌中市场份额最大的一个品牌了&#xff0c;在全球证书市场份额占比约为40%。 其超高的市场份额占比主要还是基于其超…

021、Python+fastapi,第一个Python项目走向第21步:ubuntu 24.04 docker 安装mysql8集群、redis集群(二)

系列文章目录 pythonvue3fastapiai 学习_浪淘沙jkp的博客-CSDN博客https://blog.csdn.net/jiangkp/category_12623996.html 前言 安装redis 我会以三种方式安装&#xff0c;在5月4号修改完成 第一、直接最简单安装&#xff0c;适用于测试环境玩玩 第二、conf配置安装 第三…

【Leetcode 42】 接雨水

基础思路&#xff1a; &#xff08;1&#xff09;需要将问题最小化&#xff0c;首先计算第i个位置最多容纳多少雨水&#xff08;细长的一条水柱&#xff09;&#xff0c;然后求和就是总的雨水量&#xff1b; &#xff08;2&#xff09;第i个位置容纳雨水量 min(左侧最高, 右…

​《MATLAB科研绘图与学术图表绘制从入门到精通》示例:绘制德国每日风能和太阳能产量3D线图

在MATLAB中&#xff0c;要绘制3D线图&#xff0c;可以使用 plot3 函数。 在《MATLAB科研绘图与学术图表绘制从入门到精通》书中通过绘制德国每日风能和太阳能产量3D线图解释了如何在MATLAB中绘制3D线图。 购书地址&#xff1a;https://item.jd.com/14102657.html

牛客热题:单链表排序

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 牛客热题&#xff1a;单链表排序题目链接方法一&…

【XR806开发板试用】基于MQTT与Cjson库的花式点灯

一、项目介绍 久闻openharmony大名&#xff0c;一直没有机会接触&#xff0c;感谢极术社区和全志社区的这次活动&#xff0c;让我能够了解并上手这个系统。 openhamony 1.1的内核是基于liteos内核系统进行构建的&#xff0c;liteos作为物联网系统&#xff0c;结合xr806小型开…

美团KV存储squirrel和Celler学习

文章目录 美团在KV存储squirrel优化和改进在水平方向1、对Gossip协议进行优化 在垂直扩展方面1、forkless RDB数据复制优化2、使用多线程&#xff0c;充分利用机器的多核能力 在高可用方面 美团持久化kv存储celler优化和改进水平扩展优化1、使用bulkload进行数据导入2、线程模型…

Adobe系列软件安装

双击解压 先运行Creative_Cloud_Set_Up.exe。 完毕后&#xff0c;运行AdobeGenP.exe 先Path&#xff0c;选路径&#xff0c;如 C:\Program Files\Adobe 后Search 最后Patch。 关闭软件&#xff0c;修图&#xff01;

电力能源箱3D可视化:开启智慧能源管理新篇章

随着科技的不断进步&#xff0c;电力能源箱的管理与维护逐渐向着智能化、可视化的方向发展。3D可视化技术的崛起&#xff0c;不仅极大地提升了能源管理的效率&#xff0c;更以其直观、生动的特点&#xff0c;引领着电力能源管理领域迈入了一个全新的时代。 电力能源箱作为电力系…

解决一个朋友的nbcio-boot的mysql数据库问题

1、原先安装mysql5.7数据库&#xff0c;导入我的项目里的带数据有报错信息 原因不明 2、只能建议用docker进行msyql5.7的安装 如下&#xff0c;可以修改成自己需要的信息 docker run -p 3306:3306 --name mastermysql -v /home/mydata/mysql/data:/var/lib/mysql -e MYSQL_R…

为什么感觉没有效果

以前在辅导小儿作业的时候&#xff0c;我会在常用的搜索引擎里去寻找答案&#xff0c;一般情况下都能解决问题。 但是最近一段时间&#xff0c;我发现&#xff0c;搜索引擎搜出来的结果还没有利用短视频搜出来的答案更全面&#xff0c;短视频软件不仅可以显示AI整理出来的答案…

js api part4

其他事件 页面加载事件 外部资源&#xff08;如图片、外联CSS和JavaScript等&#xff09;加载完毕时触发的事件 原因&#xff1a;有些时候需要等页面资源全部处理完了做一些事情&#xff0c;老代码喜欢把 script 写在 head 中&#xff0c;这时候直接找 dom 元素找不到。 事件…

2010-2022年上市公司彭博ESG披露评分、分项得分数据

2010-2022年上市公司彭博ESG披露评分、分项得分数据 1、时间&#xff1a;2010-2022年 2、来源&#xff1a;Bloomberg ESG 指数 3、指标&#xff1a;股票代码、股票简称、年份、ESG披露评分、环境披露评分、社会信息披露评分、治理披露评分 4、范围&#xff1a;上市公司 5、…

OpenNJet:下一代云原生应用引擎

OpenNJet&#xff1a;下一代云原生应用引擎 前言一、技术架构二、新增特性1. 透明流量劫持2. 熔断机制3. 遥测与故障注入 三、Ubuntu 发行版安装 OpentNJet1. 添加gpg 文件2. 添加APT 源3. 安装及启动4. 验证 总结 前言 OpenNJet&#xff0c;是一款基于强大的 NGINX 技术栈构建…

Java苍穹外卖04-

一、缓存菜品 1.问题说明 2.实现思路 就是点击到这个分类的时候就可以展示相应的菜品数据 3.代码实现 在user的菜品的contoller中&#xff1a;增加判断redis中是否存在所需数据&#xff0c;不存在添加&#xff0c;存在直接取得 这里注意&#xff1a;你放进去用的是List<Di…

【Osek网络管理测试】[TG3_TC3]tSleepRequestMin_L

&#x1f64b;‍♂️ 【Osek网络管理测试】系列&#x1f481;‍♂️点击跳转 文章目录 1.环境搭建2.测试目的3.测试步骤4.预期结果5.测试结果 1.环境搭建 硬件&#xff1a;VN1630 软件&#xff1a;CANoe 2.测试目的 验证DUT进入NMLimpHome状态后请求睡眠的最短时间是否正确…

Flink时间语义 | 大数据技术

⭐简单说两句⭐ ✨ 正在努力的小叮当~ &#x1f496; 超级爱分享&#xff0c;分享各种有趣干货&#xff01; &#x1f469;‍&#x1f4bb; 提供&#xff1a;模拟面试 | 简历诊断 | 独家简历模板 &#x1f308; 感谢关注&#xff0c;关注了你就是我的超级粉丝啦&#xff01; &a…

SpringBoot+Vue+Element-UI实现学生综合成绩测评系统

前言介绍 学生成绩是高校人才培养计划的重要组成部分&#xff0c;是实现人才培养目标、培养学生科研能力与创新思维、检验学生综合素质与实践能力的重要手段与综合性实践教学环节。而学生所在学院多采用半手工管理学生成绩的方式&#xff0c;所以有必要开发学生综合成绩测评系…

校园寄取快递代拿小程序源码系统 带完整的安装代码包以及搭建教程

在数字化快速发展的今天&#xff0c;校园生活也在不断地与时俱进&#xff0c;向着更加便捷、高效的方向迈进。为了满足学生们对于快递寄取代拿的便捷需求&#xff0c;小编给大家分享一款校园寄取快递代拿小程序源码系统&#xff0c;该系统不仅提供了完整的安装代码包&#xff0…
最新文章