iOS ------ JSONModel源码

news2024/9/22 17:24:10

一,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.coloradmin.cn/o/1645596.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

yolov5-pytorch-Ultralytics训练+预测+报错处理记录

一、前言 玩一段时间大模型&#xff0c;也该回归一下图像识别。本项目用于记录使用基于Ultralytics的yolov5进行目标检测测试。为什么用Ultralytics呢&#xff1f;答案有3 1、其良好的生态&#xff0c;方便我们部署到其它语言和设备上。因此本次测试结论&#xff1a;大坑没有&…

华为手机连接电脑后电脑无反应、检测不到设备的解决方法

本文介绍华为手机与任意品牌电脑连接时&#xff0c;出现连接后电脑无反应、检测不到手机连接情况的解决方法。 最近&#xff0c;因为手机的存储空间愈发紧缺&#xff0c;所以希望在非华为电脑中&#xff0c;将华为手机内的照片、视频等大文件备份、整理一下。因此&#xff0c;需…

2024年化学材料、清洁能源与生物技术国际学术会议(ICCMCEB2024)

2024年化学材料、清洁能源与生物技术国际学术会议(ICCMCEB2024) 会议简介 2024国际化学材料、清洁能源和生物技术大会&#xff08;ICCMCEB2024&#xff09;将在长沙隆重举行。本次会议旨在汇聚来自世界各地的化学材料、清洁能源和生物技术领域的专家学者&#xff0c;共同探…

Vite构造Vue3

环境安装 node.js安装-CSDN博客 初始化Vue项目安装脚手架_vue init webpack安装脚手架-CSDN博客 选择Vue框架 &#xff0c;项目名称可以自定义&#xff0c;我使用默认的 vite-project 选择JS 进入项目安装依赖 安装路由

创新指南|组织健康仍然是企业创新长期绩效的关键

麦肯锡关于组织健康的最新调查结果表明&#xff0c;它仍然是当今全球市场中价值创造的最佳预测者和竞争优势的可持续来源。在本文中&#xff0c;我们将探讨最新的 OHI 结果&#xff0c;并重点介绍该指数揭示的有关领导力、数据和技术以及人才管理的一些更引人注目的见解。我们还…

【华为】IPSec VPN手动配置

【华为】IPSec VPN手动配置 拓扑配置ISP - 2AR1NAT - Easy IPIPSec VPN AR3NATIPsec VPN PC检验 配置文档AR1AR2 拓扑 配置 配置步骤 1、配置IP地址&#xff0c;ISP 路由器用 Lo0 模拟互联网 2、漳州和福州两个出口路由器配置默认路由指向ISP路由器 3、进行 IPsec VPN配置&…

数据结构===树

文章目录 概要概念相关概念 有哪些常用的树小结 概要 树是一种新的数据结构&#xff0c;不同于数组&#xff0c;链表。就像大自然中的树&#xff0c;看下这个数据结构&#xff0c;很有意思&#xff0c;有一个主干&#xff0c;然后还有很多树叉&#xff0c;即支干。不错&#xf…

SpringMVC响应数据

三、SpringMVC响应数据 3.1 handler方法分析 理解handler方法的作用和组成&#xff1a; /*** TODO: 一个controller的方法是控制层的一个处理器,我们称为handler* TODO: handler需要使用RequestMapping/GetMapping系列,声明路径,在HandlerMapping中注册,供DS查找!* TODO: ha…

Autodesk AutoCAD 2025 for Mac:强大的二维三维绘图工具

Autodesk AutoCAD 2025 for Mac是一款专为Mac用户打造的计算机辅助设计软件&#xff0c;它在继承了AutoCAD系列软件的优秀传统的基础上&#xff0c;针对Mac系统进行了全面优化&#xff0c;为用户提供了更出色的绘图和设计体验。 这款软件不仅支持用户创建和编辑复杂的二维几何图…

python 中如何匹配字符串

python 中如何匹配字符串&#xff1f; 1. re.match 尝试从字符串的起始位置匹配一个模式&#xff0c;如果不是起始位置匹配成功的话&#xff0c;match()就返回none。 import re line"this hdr-biz 123 model server 456" patternr"123" matchObj re.matc…

打破地域界限:海外短剧小程序引领全球影视潮流

海外短剧小程序在打破地域界限、引领全球影视潮流的创作内容方面&#xff0c;展现了巨大的潜力和价值。这些小程序通过提供跨文化的影视内容&#xff0c;促进了全球观众之间的文化交流与理解&#xff0c;同时也为全球影视创作者提供了一个展示才华的平台。 首先&#xff0c;海外…

Swift 周报 第五十期

文章目录 前言新闻和社区WWDC24&#xff1a;6 月 10 日至 14 日 (太平洋时间)苹果“内忧外患”&#xff0c;库克中国求援苹果被起诉&#xff01;市值一夜蒸发8000亿元 提案通过的提案正在审查的提案拒绝的提案 Swift论坛推荐博文话题讨论关于我们 前言 本期是 Swift 编辑组自主…

某东抢购某台脚本-低调

某东抢购某台脚本 小白操作-学习使用 注意&#xff1a; 本文部分变量已做脱敏处理&#xff0c;仅用于测试和学习研究&#xff0c;禁止用于商业用途&#xff0c;不能保证其合法性&#xff0c;准确性&#xff0c;完整性和有效性&#xff0c;请根据情况自行判断。技术层面需要提…

React 第二十一章 Portals

Portals 被翻译成传送门&#xff0c;是 React 库中的一个特性&#xff0c;它允许开发者将子组件渲染到父组件 DOM 层次结构之外的其他地方。 React 组件通常是在其父组件的 DOM 层次结构中渲染的&#xff0c;这意味着它们的输出会被插入到父组件的某个 DOM 元素中。然而&#…

图像处理-图像平滑

图像平滑 前言一、概念介绍1.1 图像的平滑1.2 图像中噪声的分类1.3 MATLAB的添加噪音代码 二、空间域平滑滤波2.1 均值滤波2.2 原理计算 总结 前言 在图像的获取、传输和存储过程常常收到各种噪声的干扰和影响&#xff0c;使得图像的质量下降&#xff0c;为了获得高质量的数字…

云仓酒庄携手央视共筑品牌新高度,酒类行业广告战略迈向新征程

随着酒类市场的日益繁荣与竞争的加剧&#xff0c;品牌宣传与推广在酒类行业中的地位愈发凸显。近日&#xff0c;云仓酒庄宣布与中视中州&#xff08;央视代理机构&#xff09;达成2024-2025年度央视广告战略合作&#xff0c;云仓酒庄副总裁周玄代表云仓酒庄签约&#xff0c;这一…

VTK —— 三、图形格式 - 示例1 - 读取.vtp文件并输出.ply文件(附完整源码)

代码效果&#xff1a;演示程序读取.vtp后输出.ply文件&#xff0c;使用paraview打开该输出的.ply文件 本代码编译运行均在如下链接文章生成的库执行成功&#xff0c;若无VTK库则请先参考如下链接编译vtk源码&#xff1a; VTK —— 一、Windows10下编译VTK源码&#xff0c;并用V…

电销卡与电话管家是什么

防封电销卡是啥&#xff1f; 也许有的人并不是很清晰&#xff0c;实际上防封电销卡也是电销业务流程运用避免封号的一种手机卡&#xff0c;它的作用实际上跟一般的用号码卡语音通话是类似的&#xff0c;唯独不一样的是防封电销卡是加入白名单的&#xff0c;让电销业务在开展的过…

平面分割--------PCL

平面分割 bool PclTool::planeSegmentation(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, pcl::ModelCoefficients::Ptr coefficients, pcl::PointIndices::Ptr inliers) {std::cout << "Point cloud data: " << cloud->points.size() <<…

【LLama】Llama3 的本地部署与lora微调(基于xturn)

系列课程代码文档&#xff08;前2节课可跳过&#xff09;&#xff1a;https://github.com/SmartFlowAI/Llama3-Tutorial 课程视频&#xff1a;https://space.bilibili.com/3546636263360696/channel/series XTuner &#xff1a;https://github.com/InternLM/xtuner/blob/main/R…