zoukankan      html  css  js  c++  java
  • (20170207)开源第三方学习之JSONModel

     
    主要作用是把JSON字符串转成Model实体,也可以把实体转化成JSON字符串;还包含一些转字典的内容;JOSN字符串通过AFNetworking转化成字典,然后再能过字典跟实体进行转换;
     
    2:原理实现:
     
    主要介绍关于实体对象转换地原理,其它不在这个里面进行介绍;其主要还是运用运用时的机制,对属性进行获取换进行处理; 
    -(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
    {
        //check for nil input
        //1.为空判断
        if (!dict) {
            if (err) *err = [JSONModelError errorInputIsNil];
            return nil;
        }
    
        //invalid input, just create empty instance
        //2.类型判断
        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
        //3.核心,初始化映射property
        self = [self init];
        if (!self) {
    
            //super init didn't succeed
            if (err) *err = [JSONModelError errorModelIsInvalid];
            return nil;
        }
    
        //check incoming data structure
        //4.检查映射结构是否能够从dictionary中找到相应的数据
        if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
            return nil;
        }
    
        //import the data from a dictionary
        //5.进行数据赋值
        if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
            return nil;
        }
    
        //run any custom model validation
        //6.本地数据检查
        if (![self validate:err]) {
            return nil;
        }
    
        //model is valid! yay!
        return self;
    }

    2.1:如何获得属性相关修饰符?采用运行时的机制,获取实体中的属性,并通过属性获取对应的修饰符;不同的字符跟符号代表不一样的修饰符;
     
    代码: 
            unsigned int propertyCount;
            objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
    
            //loop over the class properties
            for (unsigned int i = 0; i < propertyCount; i++) {
    
                JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
    
                //get property name
                objc_property_t property = properties[i];
                const char *propertyName = property_getName(property);
                p.name = @(propertyName);
    
                //JMLog(@"property: %@", p.name);
    
                //get property attributes
                const char *attrs = property_getAttributes(property);
                NSString* propertyAttributes = @(attrs);
                NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@",”];
                …….
    }

    property_getAttributes函数返回objc_property_attribute_t结构体列表,objc_property_attribute_t结构体包含name和value,常用的属性如下:
     
    属性类型  name值:T  value:变化
    编码类型  name值:C(copy) &(strong) W(weak)空(assign) 等 value:无
    非/原子性 name值:空(atomic) N(Nonatomic)  value:无
    变量名称  name值:V  value:变化
     
     
    使用property_getAttributes获得的描述是property_copyAttributeList能获取到的所有的name和value的总体描述,如 T@"NSDictionary",C,N,V_dict1
     
    2.2 通过NSScanner用于在字符串中扫描指定的字符,尤其是把它们翻译/转换为数字和别的字符串。可以在创建NSScaner时指定它的string属性,然后scanner会按照你的要求从头到尾地扫描这个字符串的每个字符。 
    NSScanner* scanner = nil;
                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);
                    p.type = NSClassFromString(propertyType);
                    p.isMutable = ([propertyType rangeOfString:@"Mutable"].location != NSNotFound);
                    p.isStandardJSONType = [allowedJSONTypes containsObject:p.type];
    
                    //read through the property protocols
                    while ([scanner scanString:@"<" intoString:NULL]) {
    
                        NSString* protocolName = nil;
    
                        [scanner scanUpToString:@">" intoString: &protocolName];
    
                        if ([protocolName isEqualToString:@"Optional"]) {
                            p.isOptional = YES;
                        } else if([protocolName isEqualToString:@"Index"]) {
                            p.isIndex = YES;
                            objc_setAssociatedObject(
                                                     self.class,
                                                     &kIndexPropertyNameKey,
                                                     p.name,
                                                     OBJC_ASSOCIATION_RETAIN // This is atomic
                                                     );
                        } else if([protocolName isEqualToString:@"ConvertOnDemand"]) {
                            p.convertsOnDemand = YES;
                        } else if([protocolName isEqualToString:@"Ignore"]) {
                            p = nil;
                        } else {
                            p.protocol = protocolName;
                        }
    
                        [scanner scanString:@">" intoString:NULL];
                    }
    
                }
                //check if the property is a 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
                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 mistaked -> 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];
                    }
    
                }

    然后对符号的字符串进行解析分析,把相应的字符串获取到进行判断处理;对于有增加相应的属性进行操作,如忽略、可为空等,如是block修饰的也直接置空属性,不对它进行转换;代码如下:

    NSString *nsPropertyName = @(propertyName);
                if([[self class] propertyIsOptional:nsPropertyName]){
                    p.isOptional = YES;
                }
    
                if([[self class] propertyIsIgnored:nsPropertyName]){
                    p = nil;
                }
    
                //few cases where JSONModel will ignore properties automatically
                if ([propertyType isEqualToString:@"Block"]) {
                    p = nil;
                }

    2.3 JSONModel关于keyMapper修改对应的属性对应名称
     
    下面这段平时我们用于替换Model跟JSON字符串不一样的地方,重新给它命名一个属性新名字; 
     
    +(JSONKeyMapper*)keyMapper
    {
        return [[JSONKeyMapper alloc] initWithDictionary:@{
                                                           @"new_password": @"my_new_password",
                                                           }];
    }

    其实是在JSONModel初始化过程中有对keyMapper进行一个判断处理;判断当前的实体对象中是否有这个方法,如果有且还没有被映射缓存过,则对它进行处理;

        id mapper = [[self class] keyMapper];
        if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
            objc_setAssociatedObject(
                                     self.class,
                                     &kMapperObjectKey,
                                     mapper,
                                     OBJC_ASSOCIATION_RETAIN // This is atomic
                                     );
        }

    其中关于&kClassPropertiesKey, &kMapperObjectKey的运用说明,是映射缓存的标识;

      //使用AssociateObject进行映射property的缓存,判断是否映射过
        if (!objc_getAssociatedObject(self.class, &kClassPropertiesKey)) {
            [self __inspectProperties];
        }
    
    //同样使用AssociateObject进行映射property的缓存
    if ( mapper && !objc_getAssociatedObject(self.class, &kMapperObjectKey) ) {
    }

    写入标识的操作如下:

        objc_setAssociatedObject(
                                 self.class,
                                 &kClassPropertiesKey,
                                 [propertyIndex copy],
                                 OBJC_ASSOCIATION_RETAIN // This is atomic
                                 );

    2.4 关于健值是否缺少判断
     
    获取到当前实体的属性值,对于那些可空的就不进行判断,也就没有放在下面这个NSMutableSet里面 
    -(NSMutableSet*)__requiredPropertyNames
    {
        //fetch the associated property names
        NSMutableSet* classRequiredPropertyNames = objc_getAssociatedObject(self.class, &kClassRequiredPropertyNamesKey);
    
        if (!classRequiredPropertyNames) {
            classRequiredPropertyNames = [NSMutableSet set];
            [[self __properties__] enumerateObjectsUsingBlock:^(JSONModelClassProperty* p, NSUInteger idx, BOOL *stop) {
                if (!p.isOptional) [classRequiredPropertyNames addObject:p.name];
            }];
    
            //persist the list
            objc_setAssociatedObject(
                                     self.class,
                                     &kClassRequiredPropertyNamesKey,
                                     classRequiredPropertyNames,
                                     OBJC_ASSOCIATION_RETAIN // This is atomic
                                     );
        }
        return classRequiredPropertyNames;
    }

    这边要兼容除了dic字典里面的属性还要处理如果有KeyMapper的替换属性;然后进行统一判断;

    -(BOOL)__doesDictionary:(NSDictionary*)dict matchModelWithKeyMapper:(JSONKeyMapper*)keyMapper error:(NSError**)err
    {
        //check if all required properties are present
        NSArray* incomingKeysArray = [dict allKeys];
        NSMutableSet* requiredProperties = [self __requiredPropertyNames];
        NSSet* incomingKeys = [NSSet setWithArray: incomingKeysArray];
    
        //transform the key names, if neccessary
        if (keyMapper || globalKeyMapper) {
    
            NSMutableSet* transformedIncomingKeys = [NSMutableSet setWithCapacity: requiredProperties.count];
            NSString* transformedName = nil;
    
            //loop over the required properties list
            for (JSONModelClassProperty* property in [self __properties__]) {
    
                transformedName = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper importing:YES] : property.name;
    
                //chek if exists and if so, add to incoming keys
                id value;
                @try {
                    value = [dict valueForKeyPath:transformedName];
                }
                @catch (NSException *exception) {
                    value = dict[transformedName];
                }
    
                if (value) {
                    [transformedIncomingKeys addObject: property.name];
                }
            }
    
            //overwrite the raw incoming list with the mapped key names
            incomingKeys = transformedIncomingKeys;
        }
    
        //check for missing input keys
        if (![requiredProperties isSubsetOfSet:incomingKeys]) {
    
            //get a list of the missing properties
            [requiredProperties minusSet:incomingKeys];
    
            //not all required properties are in - invalid input
            JMLog(@"Incoming data was invalid [%@ initWithDictionary:]. Keys missing: %@", self.class, requiredProperties);
    
            if (err) *err = [JSONModelError errorInvalidDataWithMissingKeys:requiredProperties];
            return NO;
        }
    
        //not needed anymore
        incomingKeys= nil;
        requiredProperties= nil;
    
        return YES;
    }

    2.5 开始进行赋值操作
     
    首先获取到对应的属性列表数组 
    -(NSArray*)__properties__
    {
        //fetch the associated object
        NSDictionary* classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey);
        if (classProperties) return [classProperties allValues];
    
        //if here, the class needs to inspect itself
        [self __setup__];
    
        //return the property list
        classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey);
        return [classProperties allValues];
    }

    然后主要进行属性赋值

    -(BOOL)__importDictionary:(NSDictionary*)dict withKeyMapper:(JSONKeyMapper*)keyMapper validation:(BOOL)validation error:(NSError**)err
    {
        //loop over the incoming keys and set self's properties
        //循环遍历映射出来的JSONModelClassProperty结构体
        for (JSONModelClassProperty* property in [self __properties__]) {
    
            //convert key name ot model keys, if a mapper is provided
            //keyMapper映射,获取镇真正的值
            NSString* jsonKeyPath = (keyMapper||globalKeyMapper) ? [self __mapString:property.name withKeyMapper:keyMapper importing:YES] : property.name;
            //JMLog(@"keyPath: %@", jsonKeyPath);
    
            //general check for data type compliance
            id jsonValue;
            @try {
                jsonValue = [dict valueForKeyPath: jsonKeyPath];
            }
            @catch (NSException *exception) {
                jsonValue = dict[jsonKeyPath];
            }
    
            //check for Optional properties
            if (isNull(jsonValue)) {
                //skip this property, continue with next property
                if (property.isOptional || !validation) continue;
    
                if (err) {
                    //null value for required property
                    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;
    
            //判断数据输入类型是不是允许的json类型
            for (Class allowedType in allowedJSONTypes) {
                if ( [jsonValueClass isSubclassOfClass: allowedType] ) {
                    isValueOfAllowedType = YES;
                    break;
                }
            }
    
            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;
            }
    
            //check if there's matching property in the model
            if (property) {
    
                // check for custom setter, than the model doesn't need to do any guessing
                // how to read the property's value from JSON
                // 使用对象相应的setter方法进行set
                if ([self __customSetValue:jsonValue forProperty:property]) {
                    //skip to next JSON key
                    continue;
                };
    
                // 0) handle primitives
                // 代表基础类型,比如int float等,直接使用kvc赋值
                if (property.type == nil && property.structName==nil) {
    
                    //generic setter
                    if (jsonValue != [self valueForKey:property.name]) {
                        [self setValue:jsonValue forKey: property.name];
                    }
    
                    //skip directly to the next key
                    continue;
                }
    
                // 0.5) handle nils
                if (isNull(jsonValue)) {
                    if ([self valueForKey:property.name] != nil) {
                        [self setValue:nil forKey: property.name];
                    }
                    continue;
                }
    
    
                // 1) check if property is itself a JSONModel
                // 判断子结构是否是一个JSONModel结构,进行递归遍历,先将子结构遍历完并赋值完成
                if ([self __isJSONModelSubClass:property.type]) {
    
                    //initialize the property's model, store it
                    JSONModelError* initErr = nil;
                    id value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];
    
                    if (!value) {
                        //skip this property, continue with next property
                        if (property.isOptional || !validation) continue;
    
                        // Propagate the error, including the property name as the key-path component
                        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];
                    }
    
                    //for clarity, does the same without continue
                    continue;
    
                } else {
    
                    // 2) check if there's a protocol to the property
                    //  ) might or not be the case there's a built in transform for it
                    // 是否包含protocol的字段,该字段主要用来表明array或者dictionary中的对象类型
                    if (property.protocol) {
    
                        //JMLog(@"proto: %@", p.protocol);
                        //循环遍历子内容,将对应的类型赋给相应的array或者dictionary
                        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;
                        }
                    }
    
                    // 3.1) handle matching standard JSON types
                    // 判断标准的json类型,比如nsstring等
                    if (property.isStandardJSONType && [jsonValue isKindOfClass: property.type]) {
    
                        //mutable properties
                        if (property.isMutable) {
                            jsonValue = [jsonValue mutableCopy];
                        }
    
                        //set the property value
                        if (![jsonValue isEqual:[self valueForKey:property.name]]) {
                            [self setValue:jsonValue forKey: property.name];
                        }
                        continue;
                    }
    
                    // 3.3) handle values to transform
                    // 其他处理情况,主要是一些类型转换的情况,比如nsstring转换为nsurl等
                    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)
                        // 获取真实的json数据类型
                        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
                        // 通过property类型和json数据类型进行转换的判断
                        NSString* selectorName = [NSString stringWithFormat:@"%@From%@:",
                                                  (property.structName? property.structName : property.type), //target name
                                                  sourceClass]; //source name
                        SEL selector = NSSelectorFromString(selectorName);
    
                        //check for custom transformer
                        //是否有本地转换的方法
                        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;
                            }
                        }
    
                        //check if there's a transformer with that name
                        if (foundCustomTransformer) {
    
                            //it's OK, believe me...
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                            //transform the value
                            // 通过 JSONValueTransformer 进行类型转换
                            jsonValue = [valueTransformer performSelector:selector withObject:jsonValue];
    #pragma clang diagnostic pop
    
                            if (![jsonValue isEqual:[self valueForKey:property.name]]) {
                                [self setValue:jsonValue forKey: property.name];
                            }
    
                        } else {
    
                            // it's not a JSON data type, and there's no transformer for it
                            // if property type is not supported - that's a programmer mistake -> exception
                            @throw [NSException exceptionWithName:@"Type not allowed"
                                                           reason:[NSString stringWithFormat:@"%@ type not supported for %@.%@", property.type, [self class], property.name]
                                                         userInfo:nil];
                            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;
    }

    其中获取到值对应的类型是否在支持的转化类型中

    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"
                ];

    然后上面的代码用于下面进行判断

    Class jsonValueClass = [jsonValue class];
            BOOL isValueOfAllowedType = NO;
    
            for (Class allowedType in allowedJSONTypes) {
                if ( [jsonValueClass isSubclassOfClass: allowedType] ) {
                    isValueOfAllowedType = YES;
                    break;
                }
            }

    2.6 定义空的协议用于区分一些属性标识

    @protocol Ignore
    @end
    
    /**
    * Protocol for defining optional properties in a JSON Model class. Use like below to define
    * model properties that are not required to have values in the JSON input:
    *
    * @property (strong, nonatomic) NSString&lt;Optional&gt;* propertyName;
    *
    */
    @protocol Optional
    @end
    
    /**
    * Protocol for defining index properties in a JSON Model class. Use like below to define
    * model properties that are considered the Model's identifier (id).
    *
    * @property (strong, nonatomic) NSString&lt;Index&gt;* propertyName;
    *
    */
    @protocol Index
    @end
    
    //使所有对象可选兼容,避免编译器警告
    @interface NSObject(JSONModelPropertyCompatibility)<Optional, Index, Ignore>
    @end

    特别是Optional为可空的标识例如我们给一个属性设置可空时 @property (nonatomic, copy) NSString<Optional> *current_status;,然后在上面获取属性的字符串中关于是否存在这个标识字符串,做一些属性的配置;它是被包含在<>里面;

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

     3:关于属性类型的获取实例

    定义一个类:

    @interface MPAllModel : JSONModel
    
    @property(nonatomic,copy)NSString *name;
    @property(nonatomic,strong)NSString *userName;
    @property(nonatomic)int age;
    @property(nonatomic)float price;
    @property(nonatomic,strong)NSNumber *isShow;
    @property(nonatomic,assign)BOOL isError;
    @property(nonatomic,strong)NSArray *itemArray;
    @property(nonatomic,strong)NSArray<Optional> *dataArray;
    @end

    然后利用运行时的代码,获得到这个类对应属性的字符串内容:

        unsigned int outCount;
        int i;
        objc_property_t *property = class_copyPropertyList([MPAllModel class], &outCount);
        for (i = outCount -1; i >= 0 ; i--) {
            NSString *getPropertyName = [NSString stringWithCString:property_getName(property[i]) encoding:NSUTF8StringEncoding];
            NSLog(@"getPropertyName:        %@",getPropertyName);
            NSString *getPropertyNameString = [NSString stringWithCString:property_getAttributes(property[i]) encoding:NSUTF8StringEncoding];
            NSLog(@"getPropertyNameString:  %@",getPropertyNameString);
        }

    得到下面对应的类型:

    getPropertyName:        dataArray
    getPropertyNameString:  T@"NSArray<Optional>",&,N,V_dataArray
    
    getPropertyName:        itemArray
    getPropertyNameString:  T@"NSArray",&,N,V_itemArray
    
    getPropertyName:        isError
    getPropertyNameString:  TB,N,V_isError
    
    getPropertyName:        isShow
    getPropertyNameString:  T@"NSNumber",&,N,V_isShow
    
    getPropertyName:        price
    getPropertyNameString:  Tf,N,V_price
    
    getPropertyName:        age
    getPropertyNameString:  Ti,N,V_age
    
    getPropertyName:        userName
    getPropertyNameString:  T@"NSString",&,N,V_userName
    
    getPropertyName:        name
    getPropertyNameString:  T@"NSString",C,N,V_name

    最近有个妹子弄的一个关于扩大眼界跟内含的订阅号,每天都会更新一些深度内容,在这里如果你感兴趣也可以关注一下(嘿对美女跟知识感兴趣),当然可以关注后输入:github 会有我的微信号,如果有问题你也可以在那找到我;当然不感兴趣无视此信息;

  • 相关阅读:
    ClickHouse 详解
    SparkStreaming(二)--SparkStreaming整合Kafka
    SparkStreaming(一)--核心概念及算子
    毕设进度-3月22日
    毕设进度-3月21日
    毕设进度-3月14日
    毕设进度-3月13日
    毕设进度-3月12日
    毕设进度-3月11日
    毕设进度-3月10日
  • 原文地址:https://www.cnblogs.com/wujy/p/6373737.html
Copyright © 2011-2022 走看看