[iOS开发]JSONModel源码学习

Posted Billy Miracle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[iOS开发]JSONModel源码学习相关的知识,希望对你有一定的参考价值。

JSONModel对外给出了许多常见的初始化方法:

- (instancetype)initWithDictionary:(NSDictionary *)dict error:(NSError **)err;
- (instancetype)initWithData:(NSData *)data error:(NSError **)error;
// Create a new model instance and initialize it with the JSON from a text parameter. 
// The method assumes UTF8 encoded input text.
- (instancetype)initWithString:(NSString *)string error:(JSONModelError **)err;
// Create a new model instance and initialize it with the JSON from a text parameter using the given encoding.
- (instancetype)initWithString:(NSString *)string usingEncoding:(NSStringEncoding)encoding error:(JSONModelError **)err;

接下来,我们看一看实现代码:

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

我们发现,这几个初始化方法最终实际上都还是调用了

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

在看实现前,我们先看一看:

#pragma mark - associated objects names
static const char * kMapperObjectKey;
// 关联对象kMapperObjectKey
// 保存自定义的mapper
static const char * kClassPropertiesKey;
// 关联对象kClassPropertiesKey
// 用来保存所有属性信息的NSDictionary
static const char * kClassRequiredPropertyNamesKey;
// 关联对象kClassRequiredPropertyNamesKey
// 用来保存所有属性的名称NSSet
static const char * kIndexPropertyNameKey;

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

此外,在load方法里,定义了它的支持:

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

接下来我们来看看它的实现:

- (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
    //第三步:核心的代码,通过init方法初始化映射property
    self = [self init];
    if (!self) 

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

    //check incoming data structure
    //第四步:检查映射结构是否能从我们传入的dict中找到对应的数据,如果不能找到,就返回nil,并且抛出错误
    if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) 
        return nil;
    

    //import the data from a dictionary
    //第五步:根据传入的dict进行数据的赋值,如果赋值没有成功,就返回nil,并且抛出错误。
    if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) 
        return nil;
    

    //run any custom model validation
    //第六步:根据本地的错误来判断是否有错误,如果有错误,就返回nil,并且抛出错误。
    if (![self validate:err]) 
        return nil;
    

    //model is valid! yay!
    //第七步:返回self,model有效,耶嘿!
    return self;

我们看一下其中的第三步:

//第三步:核心的代码,通过init方法初始化映射property
self = [self init];

看一下它的实现:

- (id)init 
    self = [super init];
    if (self) 
        //do initial class setup
        [self __setup__];
    
    return self;

调用了__setup__方法。我们来看它的实现:

- (void)__setup__ 
    //if first instance of this model, generate the property list
    // 第一步:
    // 先是通过AssociateObject来判断是否进行过映射property的缓存,
    // 如果没有就使用“__inspectProperties”方法进行映射property的缓存
    if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) 
        // 进行映射property的缓存
        [self __inspectProperties];
    

    //if there's a custom key mapper, store it in the associated object
    // 第二步:判断一下,当前的keyMapper是否存在和是否进行映射过,如果没有进行映射就使用AssociateObject方法进行映射
    id mapper = [[self class] keyMapper];
    if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) 
        //第三步:进行AssociateObject映射
        objc_setAssociatedObject(
                                 self.class,
                                 &kMapperObjectKey,
                                 mapper,
                                 OBJC_ASSOCIATION_RETAIN // This is atomic
                                 );
    

key mapper主要是用来针对某些json字段名和model数据名不一致的情况。
比如"com.app.test.name":"xxx""test_name":"xxx"这样的情况,可能对应的model数据字段名为name,那如何讲着两个值进行映射,就通过key mapper来完成。
接下来解析一下__inspectProperties方法,介绍怎么进行映射property的缓存。先看实现:

//inspects the class, get's a list of the class properties
//检查类,获取类属性的列表
//它的任务是保存所有需要赋值的属性。
- (void)__inspectProperties 
    //JMLog(@"Inspect class: %@", [self class]);

    NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary];

    //temp variables for the loops
    Class class = [self class];
    NSScanner* scanner = nil;
    NSString* propertyType = nil;

    // inspect inherited properties up to the JSONModel class
    while (class != [JSONModel class]) 
        //JMLog(@"inspecting: %@", NSStringFromClass(class));
        // 先是获取当前class的property列表和个数
        unsigned int propertyCount;
        objc_property_t *properties = class_copyPropertyList(class, &propertyCount);

        //loop over the class properties
        // 遍历property
        for (unsigned int i = 0; i < propertyCount; i++) 
            // 创建一个解析和判断每个property的局部变量JSONModelClassProperty
            JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];

            //get property name
            objc_property_t property = properties[i];
            const char *propertyName = property_getName(property);
            // 获取property的名称给当前这个局部变量
            p.name = @(propertyName);

            //JMLog(@"property: %@", p.name);

            //get property attributes
            // 获取这个property的属性
            // 通过property_getAttributes获取property的encode string,解析encode string可以解析出具体property的类型
            const char *attrs = property_getAttributes(property);
            NSString* propertyAttributes = @(attrs);
            NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];

            //ignore read-only properties
            //忽略只读属性
            if ([attributeItems containsObject:@"R"]) 
                continue; //to next property
            
            
            // 扫描property属性
            scanner = [NSScanner scannerWithString: propertyAttributes];

            //JMLog(@"attr: %@", [NSString stringWithCString:attrs encoding:NSUTF8StringEncoding]);
            [scanner scanUpToString:@"T" intoString: nil];
            [scanner scanString:@"T" intoString:nil];

            //check if the property is an instance of a class
            //解析一个类,检查属性是否为类的实例
            if ([scanner scanString:@"@\\"" intoString: &propertyType]) 

                [scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\\"<"]
                                        intoString:&propertyType];

                //JMLog(@"type: %@", propertyClassName);
                // 设置property的类型
                p.type = NSClassFromString(propertyType);
                // 判断并设置property的是否是可变的
                p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound);
                // 判断property的是否我们允许的json类型
                p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];

                //read through the property protocols
                // 解析protocol的string
                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];
                

            
            //check if the property is a structure
            // 检查property是否为structure
            else if ([scanner scanString:@"" intoString: &propertyType]) 
                [scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet]
                                    intoString:&propertyType];

                p.isStandardJSONType = NO;
                p.structName = propertyType;

            
            //the property must be a primitive
            // 属性必须是基本类型,比如int float等
            else 

                //the property contains a primitive data type
                //该属性包含基元数据类型
                [scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@","]
                                        intoString:&propertyType];

                //get the full name of the primitive type
                //获取基元类型的全名
                propertyType = valueTransformer.primitivesNames[propertyType];

                if (![allowedPrimitiveTypes containsObject:propertyType]) 

                    //type not allowed - programmer mistaken -> exception
                    //类型不允许 - 程序员错误 - >异常
                    @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);
            // 判断property是不是Optional
            if([[self class] propertyIsOptional:nsPropertyName])
                p.isOptional = YES;
            
            // 判断property是不是Ignored
            if([[self class] propertyIsIgnored:nsPropertyName])
                p = nil;
            
            
            Class customClass = [[self class] classForCollectionProperty:nsPropertyName];
            if (customClass) 
                p.protocol = NSStringFromClass(customClass);
            

            //few cases where JSONModel will ignore properties automatically
            //在少数情况下,JSONModel 会自动忽略属性
            if ([propertyType isEqualToString:@"Block"]) 
                p = nil;
            

            //add the property object to the temp index
            //将属性对象添加到临时索引
            // 通过kvc去设置相应的值
            if (p && ![propertyIndex objectForKey:p.name]) 
                [propertyIndex setValue:p forKey:p.name];
            

            // generate custom setters and getter
            //生成自定义设置器和获取器
            if (p)
            
                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);

        iOS开发解决 jsonModel 属性跟系统的重复

iOS中JSONModel的使用

iOS 中JSONModel的使用

MJExtension框架源码分析

iOS - JSONModel的基本使用(OC)

IOS学习随笔