接着上篇的学习,本篇以以下两个地方为着手点继续学习。
// JSON 如何转化为Model
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary
// YYModel 代理
@protocol YYModel <NSObject>
一、 JSON 转化为Model
通常我们在用YYModel时,需要将JSON 转为Model的情况下,通常会涉及到以下三个方法:
@interface NSObject (YYModel)
+ (nullable instancetype)yy_modelWithJSON:(id)son;
@end
@interface NSArray (YYModel)
+ (nullable NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json;
@end
@interface NSDictionary (YYModel)
+ (nullable NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json;
@end
但是以上三个方法最终都会要调用一下的方法:
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
if (!dictionary || dictionary == (id)kCFNull) return nil;
if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
Class cls = [self class];
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
// 是否已经使用自定义NSDictionary 转化过来
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
// 刚好在这个地方确定 class 类型
NSObject *one = [cls new];
// 判断是否已经按专门方式处理过了,**核心重点处**
if ([one yy_modelSetWithDictionary:dictionary]) return one;
return nil;
}
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
if (!dic || dic == (id)kCFNull) return NO;
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
// 注意此处用的是object_getClass(self)
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
if (modelMeta->_keyMappedCount == 0) return NO;
// 将会从字典转过来
if (modelMeta->_hasCustomWillTransformFromDictionary) {
dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
}
/** 三种类型
void *modelMeta; ///< _YYModelMeta
void *model; ///< id (self)
void *dictionary; ///< NSDictionary (json)
*/
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);
/**
* 先了解下 CFArrayApplyFunction CFDictionaryApplyFunction
目的:遍历Array数组元素,每一次传入一个函数中进行处理
优点:遍历容器类能带来性能提升
函数处理方式(CFArrayApplierFunction):ModelSetWithPropertyMetaArrayFunction
*/
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
/**先对字典中的Value 进行处理,通常情况下一般都通过
===> ModelSetWithDictionaryFunction
===> ModelSetValueForProperty
===> ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
然后可能就直接设置完了。
*/
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
/**下面是为了适配其他情况做的处理*、
//对应类型 @{@"name":@"user.name"}
if (modelMeta->_keyPathPropertyMetas) {
CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
// 对应类型 @{@"name":@[@"name",@"oldname",@"newname"]}
if (modelMeta->_multiKeysPropertyMetas) {
CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
} else {
// 对象类型 @{@"name":@"name"}
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
// 有没有从NSDictionary 转过成有效的Model
if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
先让要转化的 object
统一都转化成我们需要的 NSDictionary
(这一步上面我没有贴出来),然后再转化为我们需要的 Model Object
,当然里面涉及到诸多判断和转化,最核心还是对YYModelMeta
的使用,然后再对其进行处理专门的ModelSetWithPropertyMetaArrayFunction
函数处理。
PS:里面YYModelMeta对象里面的属性是通过YYModel 代理的设置来的
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
/**一个结构体,包含 _YYModelMeta ,id (self),NSDictionary (json) */
ModelSetContext *context = _context;
// 分别获取结构体中部分
__unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);
if (!propertyMeta->_setter) return;
id value = nil;
// 判断三种基本的映射关系
// 并且转化成字典中 value (YYValueForKeyPath这个方法的获取)
if (propertyMeta->_mappedToKeyArray) {
value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
} else if (propertyMeta->_mappedToKeyPath) {
value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
} else {
value = [dictionary objectForKey:propertyMeta->_mappedToKey];
}
// 如果取到了有效的值,那就转化为我们需要的 Model
if (value) {
__unsafe_unretained id model = (__bridge id)(context->model);
// 核心
ModelSetValueForProperty(model, value, propertyMeta);
}
}
此时还还是仅仅去处有效的 value,还没有正式转化为 Model,这时ModelSetValueForProperty
/** Set value to model with a property meta */
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta)
里面根据 meta
的类型又进行了诸多判断,重点可以看
case YYEncodingTypeNSString:
case YYEncodingTypeNSArray:
case YYEncodingTypeNSDictionary:
当然此处是和可变的情况一起处理,就以字符串举例
case YYEncodingTypeNSString:
case YYEncodingTypeNSMutableString: {
if ([value isKindOfClass:[NSString class]]) {
// 普通字符串
if (meta->_nsType == YYEncodingTypeNSString) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
// 可变字符串
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy);
}
} else if ([value isKindOfClass:[NSNumber class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == YYEncodingTypeNSString) ?
((NSNumber *)value).stringValue :
((NSNumber *)value).stringValue.mutableCopy);
} else if ([value isKindOfClass:[NSData class]]) {
NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string);
} else if ([value isKindOfClass:[NSURL class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == YYEncodingTypeNSString) ?
((NSURL *)value).absoluteString :
((NSURL *)value).absoluteString.mutableCopy);
} else if ([value isKindOfClass:[NSAttributedString class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == YYEncodingTypeNSString) ?
((NSAttributedString *)value).string :
((NSAttributedString *)value).string.mutableCopy);
}
} break;
此处也充分体现了严谨之处啊,可变和不可变,还有就是nsType
都处于处于同一种类型下,还分分别给像NSNumber
,NSURL
,NSAttributedString
的情况处理。简单的说,明明你标明了NSString
,却给你传了一个其他类型的,这边同样都做了处理。
经过上述转化后,然后再调用下
model
的setter
方法转化成我们需要的啦。
/**
model: 谁执行
meta->_setter: 执行的方法
value: 传的值
*/
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
等着所有的属性都走一遍之后,到此基本的 JSON 转化为 Model 就结束啦。
二、 YYModel中的代理
@protocol
, 其实也是我们Model中写的最多一块
//返回一个 Dict,将 Model 属性名对映射到 JSON 的 Key。
+ (NSDictionary *)modelCustomPropertyMapper;
// 返回容器类中的所需要存放的数据类型 (以 Class 或 Class Name 的形式)。
+ (NSDictionary *)modelContainerPropertyGenericClass;
// 如果实现了该方法,则处理过程中会忽略该列表内的所有属性
+ (NSArray *)modelPropertyBlacklist ;
// 如果实现了该方法,则处理过程中不会处理该列表外的属性。
+ (NSArray *)modelPropertyWhitelist ;
进行进一步探索,发现,原来@protocol
中的实现都是在YYModelMeta
中的实现的。
@implementation _YYModelMeta
- (instancetype)initWithClass:(Class)cls {
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
if (!classInfo) return nil;
self = [super init];
// TO DO 处理,也是处理代理的地方
return self;
}
重点看一下对modelCustomPropertyMapper
的处理
// 用字典添加 所有属性的PropertyMeta对象
NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
YYClassInfo *curClassInfo = classInfo;
while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy)
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;
// 创建一个我们需要的meta对象
_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;
}
// 遍历父类的Property [while 语句中下一个]
curClassInfo = curClassInfo.superClassInfo;
}
// 判断是否为空之后的处理
if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy;
// 对三种不同的映射关系进行处理
/**
NSString *_mappedToKey ===== 映射类型 @{@"name":@"name"}
NSArray *_mappedToKeyPath ==== 映射类型 @{@"name":@"user.name"}
NSArray *_mappedToKeyArray ===== 映射类型 @[@"name",@"oldname",@"newname"]
*/
NSMutableDictionary *mapper = [NSMutableDictionary new];
NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
[customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
// 拿 上面获取到的 propertyMeta 对象
_YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
if (!propertyMeta) return;
// 删除之前映射规则,因为我们待会有新的规则
[allPropertyMetas removeObjectForKey:propertyName];
if ([mappedToKey isKindOfClass:[NSString class]]) {
if (mappedToKey.length == 0) return;
propertyMeta->_mappedToKey = mappedToKey;
NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];
// 此时表明 新的映射 是 mappedToKeyPath 规则
if (keyPath.count > 1) {
propertyMeta->_mappedToKeyPath = keyPath;
[keyPathPropertyMetas addObject:propertyMeta];
}
propertyMeta->_next = mapper[mappedToKey] ?: nil;
// 此时就是相当于 赋予 新的 规则,新的映射关系
mapper[mappedToKey] = propertyMeta;
} else if ([mappedToKey isKindOfClass:[NSArray class]]) {
// 这个地方对应的就是 mappedToKeyArray
NSMutableArray *mappedToKeyArray = [NSMutableArray new];
for (NSString *oneKey in ((NSArray *)mappedToKey)) {
if (![oneKey isKindOfClass:[NSString class]]) continue;
if (oneKey.length == 0) continue;
// 同样的判断 是哪一种映射关系
NSArray *keyPath = [oneKey componentsSeparatedByString:@"."];
if (keyPath.count > 1) {
[mappedToKeyArray addObject:keyPath];
} else {
[mappedToKeyArray addObject:oneKey];
}
// 给予新的映射关系
if (!propertyMeta->_mappedToKey) {
propertyMeta->_mappedToKey = oneKey;
propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil;
}
}
if (!propertyMeta->_mappedToKey) return;
// 给予新的映射规则后并将这个propertyMeta放到相应的数组中
propertyMeta->_mappedToKeyArray = mappedToKeyArray;
[multiKeysPropertyMetas addObject:propertyMeta];
// 在多个属性映射一个 key 的时候使用
propertyMeta->_next = mapper[mappedToKey] ?: nil;
mapper[mappedToKey] = propertyMeta;
}
}];
}
/** 这个地方就是对上面 modelCustomPropertyMapper 中的查漏补缺,
处理些没有自定义的属性,让它们属性的mappedKey等于属性名
*/
[allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
propertyMeta->_mappedToKey = name;
// 在多个属性映射一个 key 的时候使用
propertyMeta->_next = mapper[name] ?: nil;
mapper[name] = propertyMeta;
}];
到这里,我们一个我们需要的 propertyMeta
就已经产生了,然后就可以配合着上面的 字典转 Model ,基本的一套流程就可以走啦。
了解到此,对里面的实现已经有了个粗略的思路,从上面的实现发现其中的映射关系是必须先要了解的,那就是属性名和
json key
的对应
@{ @"name" : @"name"} //_mappedTokey
@{ @"name" : [@"name",@"oldname",@"newname"]} //_mappedToKeyArray
@{ @"name" : @"use.name"} //_mappedToKeyPath
@{
@"name" : @"name",
@"fullName" : @"name",
@"username" : @"name"
} // _next
特别要注意的是_next
,这种对第四种情况的处理。
感觉目前自己对
YYModel
中还是一个混乱的认识,毕竟runtime
那块东东还没有去分析,宏观上也缺乏一个很好的认识,所以下一篇学习是必须的啦。