1:倉庫地址:https://github.com/jsonmodel/jsonmodel 主要作用是把JSON字元串轉成Model實體,也可以把實體轉化成JSON字元串;還包含一些轉字典的內容;JOSN字元串通過AFNetworking轉化成字典,然後再能過字典跟實體進行轉換; 2:原理實現: 主 ...
1:倉庫地址:https://github.com/jsonmodel/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<Optional>* 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<Index>* 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