概述
iOS源码解析—YYModel(YYClassInfo)分析了如何根据OC的Class对象构建YYClassInfo对象,为接下来的JSON数据和Model转换作准备,这篇文章开始讲解NSObject+YYModel。
NSObject+YYModel.h
分析NSObject+YYModel.h文件,包括3个Category和一个protocol,分别是:
NSObject(YYModel)
NSArray(YYModel)
NSDictionary(YYModel)
YYModel <NSObject>
-
NSObject(YYModel)
负责扩展NSObject类,提供了Model相关方法,下面是代码注释:
@interface NSObject (YYModel) //根据JSON对象创建Model + (nullable instancetype)yy_modelWithJSON:(id)json; //根据NSDictionary创建Model + (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary; //将JSON对象的各字段映射到Model的各个字段 - (BOOL)yy_modelSetWithJSON:(id)json; //将NSDictionary的各字段映射到Model的各个字段 - (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic; //根据Model创建JSON对象 - (nullable id)yy_modelToJSONObject; //根据Model创建JSON数据流 - (nullable NSData *)yy_modelToJSONData; //根据Model创建JSON字符串 - (nullable NSString *)yy_modelToJSONString; //复制Model - (nullable id)yy_modelCopy; //对Model进行归档 - (void)yy_modelEncodeWithCoder:(NSCoder *)aCoder; //对Model进行解档 - (id)yy_modelInitWithCoder:(NSCoder *)aDecoder; //model的hash值 - (NSUInteger)yy_modelHash; //判断Model和参数model是否相同 - (BOOL)yy_modelIsEqual:(id)model; //输出Model的相关信息 - (NSString *)yy_modelDescription; @end
例如定义一个Student类,代码如下:
@interface College : NSObject @property (nonatomic, copy) NSString *name; @end //Student @interface Student : NSObject <YYModel> @property (nonatomic, copy) NSString *name; //名字 @property (nonatomic, assign) NSInteger age; //年龄 @property (nonatomic, strong) College *college; //学校 @end
同时创建一个字典对象,如下:
NSDictionary *studentDic = @{@"name" : @"Tomy", @"age" : @18, @"college" : @{@"name" : @"NJU"}};
调用[Student yy_modelWithDictionary:studentDic]方法,创建一个Student类型的Model对象,然后将studentDic的每个value赋值给Model相应属性。property的名称和key一一对应。在转化的过程中,可以实现Model嵌套的情况,如Student中嵌套一个College对象。
调用[Student yy_modelToJSONObject]方法,创建一个字典对象,各键值对中的key是Model相应属性的名称,value是属性值。在转化的过程中,也支持嵌套的情况。
-
NSArray(YYModel)
负责扩展NSArray类,下面是代码注释:
@interface NSArray (YYModel) //根据JSON创建Model数组(JSON是数组格式) + (nullable NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json; @end
例如有一个数组,如下:
NSArray *studentArr = @[@{@"name" : @"Tomy", @"age" : @18, @"college" : @{@"name" : @"NJU"}}, @{@"name" : @"Alex", @"age" : @19, @"college" : @{@"name" : @"Harvard"}}, @{@"name" : @"Sunny", @"age" : @20, @"college" : @{@"name" : @"Yale"}}];
调用[NSArray yy_modelArrayWithClass:Student json:studentArr]方法,创建一个Model数组,数组中的每个元素是Student对象。
-
NSDictionary(YYModel)
负责扩展NSDictionary类,下面是代码注释:
@interface NSDictionary (YYModel) //根据JSON的value和Class对象创建Model,并创建一个新字典,取json的key作为key,取Model作为value + (nullable NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json; @end
例如创建一个字典,如下:
NSDictionary *playerDic = @{@"player1" : @{@"name" : @"Tomy", @"age" : @18, @"college" : @{@"name" : @"NJU"}}, @"player2" : @{@"name" : @"Alex", @"age" : @19, @"college" : @{@"name" : @"Yale"}}};
调用[NSDictionary yy_modelDictionaryWithClass:Student json:playerDic]得到一个新的字典studentDic,如下:
字典studentDic中的key对应原字典playerDic中的key,studentDic中的Model由playerDic中的value转化得到。
-
@protocol YYModel
YYModel是iOS协议,里面声明了一些方法,调用类通过实现这些方法,实现JSON和Model之间转换过程中的特殊处理。
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper; + (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass; + (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary; + (nullable NSArray<NSString *> *)modelPropertyBlacklist; + (nullable NSArray<NSString *> *)modelPropertyWhitelist; - (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic; - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic; - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic;
下面讲一下协议中的每个方法:
-
modelCustomPropertyMapper方法,可以指定json和model转化过程中key的映射关系,如果存在以下的字典:
NSDictionary *studentDic = @{@"NAME" : @"Tomy", @"AGE" : @{@"num":@18}, @"college" : @{@"name" : @"NJU"}};
如果需要将studentDic转化成Student类型的model,需要实现如下modelCustomPropertyMapper方法:
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper { return @{@"name" : @"NAME", @"age" : @"AGE.num"}; }
返回的键值对中的key是Student的property名称,value是studentDic中的key。
-
如果Student中存在property是对象数组或者字典,实现modelContainerPropertyGenericClass方法,例如Student存在属性mobilePhones,维护一组MobilePhone对象
@interface MobilePhone : NSObject @property (nonatomic, copy) NSString *brand; @property (nonatomic, assign) NSInteger phoneNumber; @end @interface Student : NSObject <YYModel> @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSInteger age; @property (nonatomic, strong) College *college; @property (nonatomic, strong) NSArray *mobilePhones; //MobilePhone对象数组 @end NSDictionary *studentDic = @{@"name" : @"Tomy", @"age" : @18, @"college" : @{@"name" : @"NJU"}, @"mobilePhones" : @[@{@"brand" : @"iphone",@"phoneNumber" : @123456}, @{@"brand" : @"HUAWEI",@"phoneNumber" : @123456}]};
调用[Student yy_modelWithDictionary:studentDic]方法将studentDic中的mobilePhones转化成Student的mobilePhones属性,需要实现如下:
+ (nullable NSDictionary<NSString*, id>*)modelContainerPropertyGenericClass { return @{@"mobilePhones" : [MobilePhone class]}; }
-
modelCustomClassForDictionary方法用于指定生成Model的类型,如果没有实现该方法,用默认的类型,例如Student实现modelCustomClassForDictionary,如下:
+ (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary { if ([dictionary[@"name"] isEqualToString @"Graduated"]) { return [Graduated class]; } else { return [self class]; } }
调用[Student yy_modelWithDictionary:studentDic]方法,如果studentDic[@"name"]等于@"Graduated",则不创建Student类型的对象,而是创建Graduated类型的对象。
-
modelPropertyBlacklist方法和modelPropertyWhitelist方法的作用相反,如果实现了modelPropertyBlacklist,该方法返回一个数组,数组中的值指定了Model中不需要被赋值的property,例如代码如下:
+ (nullable NSArray<NSString *> *)modelPropertyBlacklist { return @[@"age", @"college"]; }
则Student中的age和college属性不会被赋值。如果实现了modelPropertyWhitelist方法,该方法返回一个数组,数组中的值指定了Model中仅需要被赋值的property,例如代码如下:
+ (nullable NSArray<NSString *> *)modelPropertyWhitelist { return @[@"age", @"college"]; }
则Student中只有age和college属性会被赋值。
modelCustomWillTransformFromDictionary:方法作用于根据JSON创建Model对象之前,该方法可以把JSON字典转化成一个新的字典。
modelCustomTransformFromDictionary:方法作用于根据JSON创建Model对象之后,该方法可以对生成的Model做一些处理。
modelCustomTransformToDictionary:方法作用于根据Model对象创建JSON之后,该方法可以对生成的JSON字典做一些处理。
-
NSObject+YYModel.m
首先定义了两个类,_YYModelMeta和_YYModelPropertyMeta,分别封装了Model的信息和Model中各属性的信息。
_YYModelMeta维护了Class的相关信息,下面是注释:
@interface _YYModelMeta : NSObject {
@package
YYClassInfo *_classInfo; //关联的YYClassInfo对象
NSDictionary *_mapper; //维护一个键值对,key是属性名,value是_YYModelPropertyMeta对象
//维护一个数组,里面的元素是_YYModelPropertyMeta对象
NSArray *_allPropertyMetas;
//维护一个数组,里面的元素是_YYModelPropertyMeta对象
NSArray *_keyPathPropertyMetas;
//维护一个数组,里面的元素是_YYModelPropertyMeta对象
NSArray *_multiKeysPropertyMetas;
//通过映射相关
NSUInteger _keyMappedCount;
//对象类型
YYEncodingNSType _nsType;
...
}
@end
_YYModelMeta是通过YYClassInfo对象的信息构建得到的。首先调用metaWithClass:cls方法,该方法如下:
+ (instancetype)metaWithClass:(Class)cls {
...
_YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls)); //从缓存中取
...
if (!meta || meta->_classInfo.needUpdate) {
meta = [[_YYModelMeta alloc] initWithClass:cls]; //创建一个新的_YYModelMeta对象
if (meta) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
dispatch_semaphore_signal(lock);
}
}
return meta;
}
维护了一个键值对cache作为缓存,用cls作为key,调用CFDictionaryGetValue方法去缓存中查找,如果有,直接返回构建好的_____YYModelMeta对象,如果没有找到或者needUpdate属性标记为true,则根据cls创建一个新的_YYModelMeta对象,并且存入缓存。这样不需要每次都创建,提高了性能。
接下来看一下initWithClass:方法,
创建一个YYClassInfo对象,在上一篇文章中分析了YYClassInfo的创建方法;
判断调用层是否实现modelPropertyBlacklist方法,如果实现了,返回一组黑名单,数组中包含的属性名不会被转化。
判断调用层是否实现modelPropertyWhitelist方法,如果实现了,返回一组白名单,不在数组中的属性名不会被转化。
如果调用层实现了modelContainerPropertyGenericClass方法,则维护一个字典genericMapper,字典的value如果是NSString类型,需要转成Class类型。
-
遍历对象的属性,生成_YYModelPropertyMeta,代码注释如下:
NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new]; YYClassInfo *curClassInfo = classInfo; while (curClassInfo && curClassInfo.superCls != nil) { for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) { if (!propertyInfo.name) continue; //黑名单过滤 if (blacklist && [blacklist containsObject:propertyInfo.name]) continue; //白名单过滤 if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue; //根据classInfo和propertyInfo创建_YYModelPropertyMeta _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo propertyInfo:propertyInfo generic:genericMapper[propertyInfo.name]]; if (!meta || !meta->_name) continue; if (!meta->_getter || !meta->_setter) continue; if (allPropertyMetas[meta->_name]) continue; allPropertyMetas[meta->_name] = meta; //存入allPropertyMetas字典中 } curClassInfo = curClassInfo.superClassInfo; } if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy; //赋值给_allPropertyMetas属性
根据classInfo信息和propertyInfo信息创建_YYModelPropertyMeta对象,并用_allPropertyMetas字典存储,字典中的key是属性名,value是_YYModelPropertyMeta对象。
-
如果调用层实现了modelCustomPropertyMapper方法,说明存在JSON字典key和属性名之间的映射关系,需要遍历这些属性,做特殊处理,如下:
if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) { NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper]; [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) { //这些属性名对应的_YYModelPropertyMeta从allPropertyMetas字典中删除 _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName]; if (!propertyMeta) return; [allPropertyMetas removeObjectForKey:propertyName]; //mappedToKey作为新的key,存入mapper,value是propertyMeta,同时对mappedToKey做一些分割处理,表示映射的路径 if ([mappedToKey isKindOfClass:[NSString class]]) { ... if (keyPath.count > 1) { propertyMeta->_mappedToKeyPath = keyPath; [keyPathPropertyMetas addObject:propertyMeta]; } propertyMeta->_next = mapper[mappedToKey] ?: nil; mapper[mappedToKey] = propertyMeta; } ... }]; }
-
维护一个_mapper字典,key是mappedToKey,对于customMapper字典中不存在的属性,mappedToKey是其本身的属性名,value是创建的propertyMeta。同时维护一个_keyPathPropertyMetas数组和_multiKeysPropertyMetas数组,专门存储customMapper中存在的,即存在映射关系的_YYModelPropertyMeta。
其中YYModelPropertyMeta类维护了property的相关信息,下面是相关注释:
@interface _YYModelPropertyMeta : NSObject { @package NSString *_name; //属性名 YYEncodingType _type; //属性类型 YYEncodingNSType _nsType; //属性如果是NSObject类型的对象,相应的类型 BOOL _isCNumber; //是否是数字类型 Class _cls; //属性的类 Class _genericCls; // SEL _getter; //getter方法 SEL _setter; //setter方法 BOOL _isKVCCompatible; //是否可以处理KVC BOOL _isStructAvailableForKeyedArchiver; //是否可以archiver/unarchiver BOOL _hasCustomClassFromDictionary; // NSString *_mappedToKey; //该属性名对应的JSON字典中的key NSArray *_mappedToKeyPath; //该属性名对应的JSON字典中的keyPath,keyPath是一个路径,例如AGE.NUM,该数组存储@[AGE,NUM] NSArray *_mappedToKeyArray; //该属性名对应的JSON字典中的key,如果@[@"age",@"num"] YYClassPropertyInfo *_info; //关联的YYClassPropertyInfo _YYModelPropertyMeta *_next; //另一个YYModelPropertyMeta对象,key名称相同 } @end
_YYModelPropertyMeta对象是通过YYClassPropertyInfo对象的信息构建得到的。
主要方法
下面分析几个重要的方法:
-
yy_modelWithJSON:方法
该方法首先调用_yy_dictionaryWithJSON:方法将JSON数据进行转化,如果JSON数据是NSString或者NSData格式,转化为字典对象,否则直接返回。然后调用yy_modelWithDictionary:方法,代码如下:
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary { ... Class cls = [self class]; //1.metaWithClass _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]; if (modelMeta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:dictionary] ?: cls; } ... //2.根据json字典对新创建的model进行赋值 NSObject *one = [cls new]; if ([one yy_modelSetWithDictionary:dictionary]) return one; return nil; }
该方法主要分为2个步骤:
根据Class对象cls创建_YYModelMeta
-
调用yy_modelSetWithDictionary:对model进行赋值,主要代码注释如下:
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) { //1、遍历dic中的key和value,调用ModelSetWithDictionaryFunction方法进行属性赋值 CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context); //2、如果_keyPathPropertyMetas存放有映射关系,调用ModelSetWithPropertyMetaArrayFunction方法进行赋值 if (modelMeta->_keyPathPropertyMetas) { CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } //3、如果_multiKeysPropertyMetas存放有映射关系,调用ModelSetWithPropertyMetaArrayFunction方法进行赋值 if (modelMeta->_multiKeysPropertyMetas) { CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } } else { CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas, CFRangeMake(0, modelMeta->_keyMappedCount), ModelSetWithPropertyMetaArrayFunction, &context); }
主要分为2种情况:
-
如果Model中不存在属性名的映射,调用CFDictionaryApplyFunction方法遍历JSON字典中的每一个键值对,调用ModelSetWithDictionaryFunction方法对每一个键值对进行处理,由于_YYModelMeta内部维护了一个mapper字典,通过mapper取出YYModelPropertyMeta对象,最终调用ModelSetValueForProperty方法进行该属性的赋值。方法如下:
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) { ModelSetContext *context = _context; __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta); //根据key在mapper中查找对应的propertyMeta,如果没有,不进行后续操作 __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)]; __unsafe_unretained id model = (__bridge id)(context->model); //调用方法进行属性赋值 while (propertyMeta) { if (propertyMeta->_setter) { ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta); } propertyMeta = propertyMeta->_next; }; }
-
如果Model中存在_keyPathPropertyMetas属性或者_multiKeysPropertyMetas属性,说明_keyPathPropertyMetas数组或者_multiKeysPropertyMetas数组中维护了一组Model中的属性,这些属性的名称存在映射关系,这种情况下调用CFArrayApplyFunction方法对keyPathPropertyMetas数组或者multiKeysPropertyMetas数组进行遍历,并调用ModelSetWithPropertyMetaArrayFunction方法对每个属性元素进行处理,该方法最终也会调用ModelSetValueForProperty方法进行属性赋值。代码注释如下:
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) { ... //根据映射关系找到JSON中的value if (propertyMeta->_mappedToKeyArray) { value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray); } else if (propertyMeta->_mappedToKeyPath) { value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath); } else { value = [dictionary objectForKey:propertyMeta->_mappedToKey]; } //进行属性赋值 if (value) { __unsafe_unretained id model = (__bridge id)(context->model); ModelSetValueForProperty(model, value, propertyMeta); } }
上述两种情况最终会调用ModelSetValueForProperty方法在赋值的时候做了类型兼容,下文中具体分析。
-
-
yy_modelToJSONObject方法
该方法调用ModelToJSONObjectRecursive方法将Model对象转化成一个字典或者数组,需要转化的Model对象可以分为3类:
第一类是一些基本类型,直接返回或者做一些处理返回,代码如下:
if ([model isKindOfClass:[NSString class]]) return model; if ([model isKindOfClass:[NSNumber class]]) return model; if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString; if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string;
第二类是NSDictionary、NSSet、NSArray等类型,它们不属于自定义的对象类型,代码如下:
if ([model isKindOfClass:[NSDictionary class]]) { //如果是NSDictionary类型,遍历字典,用递归的方式赋值给新的字典,并返回新的字典。 if ([NSJSONSerialization isValidJSONObject:model]) return model; NSMutableDictionary *newDic = [NSMutableDictionary new]; [((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description; if (!stringKey) return; id jsonObj = ModelToJSONObjectRecursive(obj); if (!jsonObj) jsonObj = (id)kCFNull; newDic[stringKey] = jsonObj; }]; return newDic; } //如果是NSSet类型,遍历NSSet,用递归的方式赋值给新的set,并返回新的set。 if ([model isKindOfClass:[NSSet class]]) { NSArray *array = ((NSSet *)model).allObjects; if ([NSJSONSerialization isValidJSONObject:array]) return array; NSMutableArray *newArray = [NSMutableArray new]; for (id obj in array) { if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) { [newArray addObject:obj]; } else { id jsonObj = ModelToJSONObjectRecursive(obj); if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj]; } } return newArray; } //如果是NSArray类型,遍历NSArray,用递归的方式赋值给新的array,并返回新的array。 if ([model isKindOfClass:[NSArray class]]) { if ([NSJSONSerialization isValidJSONObject:model]) return model; NSMutableArray *newArray = [NSMutableArray new]; for (id obj in (NSArray *)model) { if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) { [newArray addObject:obj]; } else { id jsonObj = ModelToJSONObjectRecursive(obj); if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj]; } } return newArray; } ...
第三种是自定义的Model对象类型,首先调用metaWithClass获取自定义Model的_YYModelMeta对象,通过上文知道_YYModelMeta内部维护了一个mapper字典,用于维护Model内部的属性类型信息,然后遍历mapper中的每一个元素(_YYModelPropertyMeta),根据元素的类型,进行相应的处理,如下:
[modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) { if (propertyMeta->_isCNumber) { //数值类型 value = ModelCreateNumberFromProperty(model, propertyMeta); } else if (propertyMeta->_nsType) { id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); value = ModelToJSONObjectRecursive(v); //递归调用 } else { ... } }]
如果_YYModelPropertyMeta存在mappedToKeyPath属性,需要将属性名映射到指定的key上面。
-
yy_modelArrayWithClass:json:方法
该方法通过JSON数组和Class创建Model数组,首先调用yy_modelArrayWithClass: array:方法,然后在该方法中遍历数组,数组中每个元素是字典类型,调用yy_modelWithDictionary:方法进行转化,然后将转化后的Model添加进新数组中,最后返回这个新数组。代码如下:
+ (NSArray *)yy_modelArrayWithClass:(Class)cls array:(NSArray *)arr { if (!cls || !arr) return nil; NSMutableArray *result = [NSMutableArray new]; for (NSDictionary *dic in arr) { //遍历数组 if (![dic isKindOfClass:[NSDictionary class]]) continue; NSObject *obj = [cls yy_modelWithDictionary:dic]; //dic->Model if (obj) [result addObject:obj]; //添加Model } return result; }
-
yy_modelDictionaryWithClass:json:方法
该方法和上面的方法3类似,首先调用yy_modelDictionaryWithClass:dictionary:方法,然后在该方法中遍历JSON字典,字典中的每个元素同样是字典,调用yy_modelWithDictionary:方法进行转化,然后将转化后的Model加入新的字典中,key是之前JSON字典中的key。代码如下:
+ (NSDictionary *)yy_modelDictionaryWithClass:(Class)cls dictionary:(NSDictionary *)dic { if (!cls || !dic) return nil; NSMutableDictionary *result = [NSMutableDictionary new]; for (NSString *key in dic.allKeys) { //遍历字典 if (![key isKindOfClass:[NSString class]]) continue; NSObject *obj = [cls yy_modelWithDictionary:dic[key]]; //dic[key]->Model if (obj) result[key] = obj; //添加Model } return result; }
类型转化和兼容
YYModel的总体思想是以Model属性的类型为准,如果JSON中对应名称的value的类型和Model属性类型不一致,会对value的类型进行转化,保证和Model属性的类型一致。如果兼容不了,不进行属性赋值。下面分析一下ModelSetValueForProperty方法:
该方法上文中提到该方法是用来JSON转成Model的过程中对Model中的属性进行赋值的方法,该方法做了部分基本类型的兼容:
-
如果Model的属性是数值类型,与之对应的JSON的value是其它类型,会进行转化,如下:
if (meta->_isCNumber) { NSNumber *num = YYNSNumberCreateFromID(value); ModelSetNumberToProperty(model, num, meta); if (num) [num class]; // hold the number }
YYNSNumberCreateFromID会将value转化成NSNumber类型,有可能返回nil。
-
如果Model的属性是字符串类型,会判断value的类型,进一步处理:
case YYEncodingTypeNSString: case YYEncodingTypeNSMutableString: { if ([value isKindOfClass:[NSString class]]) { ... } else if ([value isKindOfClass:[NSNumber class]]) { ... //转化成NSString } else if ([value isKindOfClass:[NSData class]]) { ... //转化成NSString } else if ([value isKindOfClass:[NSURL class]]) { ... //转化成NSString } else if ([value isKindOfClass:[NSAttributedString class]]) { ... //转化成NSString } break;
根据value类型的不同,调用不得方法转成字符串类型。
如果Model的属性是NSNumber类型,需要将value转化成NSNumber类型。
如果Model的属性是NSData、NSDate、NSURL类型等,都需要value先转成这些类型。
-
不是所有的类型都可以转化,如果属性的对象是数组、集合或者字典类型,但是value不是对应的类型,不会转化,直接返回,不进行后续的属性赋值。例如字典类型,代码如下:
case YYEncodingTypeNSDictionary: case YYEncodingTypeNSMutableDictionary: { if ([value isKindOfClass:[NSDictionary class]]) { //value为字典类型是,进行处理,否则不处理 ... } } break;
其它方法
YYModel还提供了一些工具方法,下面简单分析一下:
-
ModelDescription方法,用来描述该Model对象的相关信息,一般用于log输出。该方法主要是根据Model对象的类型,做不同的处理:
如果是一些简单类型,做一些处理后直接输出,下面是代码注释:
case YYEncodingTypeNSNumber: case YYEncodingTypeNSDecimalNumber: case YYEncodingTypeNSDate: case YYEncodingTypeNSURL: { return [NSString stringWithFormat:@"%@",model]; }
如果是集合、数组或者字典类型,需要遍历其中每个元素,递归调用ModelDescription,生成每个元素的输出信息,拼接成一个字符串输出,例如是NSArray:
case YYEncodingTypeNSArray: case YYEncodingTypeNSMutableArray: { NSArray *array = (id)model; NSMutableString *desc = [NSMutableString new]; if (array.count == 0) { return [desc stringByAppendingString:@"[]"]; } else { [desc appendFormat:@"[\n"]; for (NSUInteger i = 0, max = array.count; i < max; i++) { NSObject *obj = array[i]; [desc appendString:@" "]; //生成每个元素的输出信息,拼接字符串 [desc appendString:ModelDescriptionAddIndent(ModelDescription(obj).mutableCopy, 1)]; [desc appendString:(i + 1 == max) ? @"\n" : @";\n"]; } [desc appendString:@"]"]; return desc; } }
如果是自定义Model对象,则遍历对象中每个属性,获取与之对应的_YYModelPropertyMeta对象,进一步判断_YYModelPropertyMeta对象的类型,做不同的处理。如果属于NSObject类型,递归调用ModelDescription方法。代码如下:
for (NSUInteger i = 0, max = properties.count; i < max; i++) { _YYModelPropertyMeta *property = properties[i]; NSString *propertyDesc; if (property->_isCNumber) { NSNumber *num = ModelCreateNumberFromProperty(model, property); propertyDesc = num.stringValue; } else { switch (property->_type & YYEncodingTypeMask) { case YYEncodingTypeObject: { id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter); propertyDesc = ModelDescription(v); if (!propertyDesc) propertyDesc = @"<nil>"; } break; } }
-
YYNSDateFromString方法,用来将字符串类型转化成NSDate类型。因为NSDate是OC的对象,而JSON数据格式中不存在日期时间类型,因此需要进行转化。通常是将具有固定格式的字符串转成NSDate类型,例如:"yyyy-MM-dd HH:mm:ss"。
该方法的主要思路是创建不同类型的NSDateFormatter对象和一组block,然后根据传入的字符串参数的格式,执行相应的block,在block中再根据字符串参数调用相应的dateFormatter进行转化。代码注释如下:
dispatch_once(&onceToken, ^{ { NSDateFormatter *formatter1 = [[NSDateFormatter alloc] init]; formatter1.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter1.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; formatter1.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss"; ... blocks[19] = ^(NSString *string) { if ([string characterAtIndex:10] == 'T') { return [formatter1 dateFromString:string]; } else { return [formatter2 dateFromString:string]; } }; ... YYNSDateParseBlock parser = blocks[string.length]; //根据string选择相应的block if (!parser) return nil; return parser(string); //执行block,选取dateFormatter进行转化,输出NSDate对象 }
结尾
YYModel作为一个负责JSON数据和Model转化的库,十分易用和高效,特别是做了一些类型的兼容和转化,避免了服务端接口数据类型和客户端Model对象类型不兼容导致的问题,例如执行了不存在的方法而导致崩溃。另一方面,对YYModel的学习在一定程度也促进了对runtime机制的学习和了解。
关于YYModel的分析到这儿先告一段落,由于本人的iOS基础有待提升,再加上表达能力有限,文中许多地方的分析和思路,表达的不是很准确和清楚,希望通过今后的学习和练习,提升自己的水平。