iOS ------ JSONModel源码

一,JSONModel的基本使用

1,基本使用方法

objectivec 复制代码
- (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):

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

具体效果:

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

输出:

objectivec 复制代码
 
   [name]: Jack
   [age]: 23
   [gender]: male

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

3,自定义错误

除了一些框架自己处理的错误(比如传入的对象不是字典),框架的作者也允许我们自己定义属于我们自己的错误。

比如,当age对应的数值小于25的时候,打印出Too young!,并阻止模型的转换

在模型的实现文件中:

objectivec 复制代码
- (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,模型嵌套

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

objectivec 复制代码
#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的实现文件里加上这一段代码:

objectivec 复制代码
@implementation Friend
@end

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

objectivec 复制代码
@property (nonatomic, copy) NSString<optional> *name;

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

objectivec 复制代码
+ (BOOL) propertyIsOptional:(NSString *)propertyName {
    return YES;
}

二,源码分析

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

objectivec 复制代码
-(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;
}
objectivec 复制代码
-(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;

}
objectivec 复制代码
-(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;
}
objectivec 复制代码
-(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;
}

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

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

2,具体看一看initWithDictionary:error:

objectivec 复制代码
-(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所持有的一些关联对象名称:

objectivec 复制代码
#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方法

定义了该框架支持的类型:

objectivec 复制代码
+(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方法:
objectivec 复制代码
-(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方法
objectivec 复制代码
-(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的所有父类的属性,并将这些属性保存到一个字典中。在将来用于和传入的字典进行映射

该字典的格式如下:

objectivec 复制代码
{
   propertyName1 = "@property propertyType1 (propertyAttributes1)";
   propertyName2 = "@property propertyType2 (propertyAttributes2)";
   ...
}

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

5,__doesDictionary:matchModelWithKeyMapper:error:方法

objectivec 复制代码
-(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:方法

objectivec 复制代码
-(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源码的具体流程图

相关推荐
若水无华2 天前
fiddler 配置ios手机代理调试
ios·智能手机·fiddler
Aress"2 天前
【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
ios·uni-app·ipa安装
Jouzzy2 天前
【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error
安全·ios·iphone
瓜子三百克2 天前
采用sherpa-onnx 实现 ios语音唤起的调研
macos·ios·cocoa
左钦杨2 天前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
努力成为包租婆2 天前
SDK does not contain ‘libarclite‘ at the path
ios
安和昂3 天前
【iOS】Tagged Pointer
macos·ios·cocoa
I烟雨云渊T3 天前
iOS 阅后即焚功能的实现
macos·ios·cocoa
struggle20253 天前
适用于 iOS 的 开源Ultralytics YOLO:应用程序和 Swift 软件包,用于在您自己的 iOS 应用程序中运行 YOLO
yolo·ios·开源·app·swift
Unlimitedz3 天前
iOS视频编码详细步骤(视频编码器,基于 VideoToolbox,支持硬件编码 H264/H265)
ios·音视频