无聊小姐姐今天准备看下YYmodel啦~ https://github.com/ibireme/YYModel
YYmodel主要是需要#import "NSObject+YYModel.h"
,也就是其实它是NSObject的一个category,我们所希望转为的model就是这里的NSObject,可以直接在model类上面用YYModel的方法得到一个model实例。
具体用法可以参考使用介绍(https://www.jianshu.com/p/25e678fa43d3),这边会直接从yy_modelWithDictionary
开始看:
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
if (!dictionary || dictionary == (id)kCFNull) return nil;
if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
// model类
Class cls = [self class];
// 得到class的一些信息
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
// 建一个新的object,并用dict填充属性
NSObject *one = [cls new];
if ([one yy_modelSetWithDictionary:dictionary]) return one;
return nil;
}
其实看起来很简洁,我们下面就分步看一下吧~ 首先是YYModelMeta
如何获取:
/// Returns the cached model class meta
+ (instancetype)metaWithClass:(Class)cls {
if (!cls) return nil;
static CFMutableDictionaryRef cache;
static dispatch_once_t onceToken;
static dispatch_semaphore_t lock;
dispatch_once(&onceToken, ^{
// 创建一个cache dict来维护class和meta的match
cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
lock = dispatch_semaphore_create(1);
});
// 每次对cache dict操作都先wait再signal,保证但线程访问cache
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
_YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
dispatch_semaphore_signal(lock);
// 如果meta没有缓存过,就新建一个
if (!meta || meta->_classInfo.needUpdate) {
meta = [[_YYModelMeta alloc] initWithClass:cls];
if (meta) {
// 把创建的meta缓存起来
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
dispatch_semaphore_signal(lock);
}
}
return meta;
}
关于信号量可以看一下:https://www.cnblogs.com/yajunLi/p/6274282.html,当信号量create时候初始化的数值,就是希望最大的线程数,所以这里
lock = dispatch_semaphore_create(1);
就是初始化了一个单线程的lock,每次对临界区操作就先wait再signal。
这里的lock就是为了保证CFMutableDictionaryRef cache的线程安全。cache里面保存了class和meta的键值对,那么meta是神马呢?
/// A class info in object model.
@interface _YYModelMeta : NSObject {
@package
YYClassInfo *_classInfo;
/// Key:mapped key and key path, Value:_YYModelPropertyMeta.
NSDictionary *_mapper;
/// Array<_YYModelPropertyMeta>, all property meta of this model.
NSArray *_allPropertyMetas;
/// Array<_YYModelPropertyMeta>, property meta which is mapped to a key path.
NSArray *_keyPathPropertyMetas;
/// Array<_YYModelPropertyMeta>, property meta which is mapped to multi keys.
NSArray *_multiKeysPropertyMetas;
/// The number of mapped key (and key path), same to _mapper.count.
NSUInteger _keyMappedCount;
/// Model class type.
YYEncodingNSType _nsType;
BOOL _hasCustomWillTransformFromDictionary;
BOOL _hasCustomTransformFromDictionary;
BOOL _hasCustomTransformToDictionary;
BOOL _hasCustomClassFromDictionary;
}
@end
大概可以看出来这就是用于记录这个class的各种信息的一个类,例如property mapper。当最开始没有的时候都是通过class init出来的:
- (instancetype)initWithClass:(Class)cls {
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
if (!classInfo) return nil;
self = [super init];
// Get black list
// 如果model实现了modelPropertyBlacklist方法,就获取一下model的modelPropertyBlacklist
// 如果实现了该方法,则处理过程中会忽略该列表内的所有属性
NSSet *blacklist = nil;
if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {
NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist];
if (properties) {
blacklist = [NSSet setWithArray:properties];
}
}
// Get white list
// 如果model实现了modelPropertyWhitelist方法,就获取一下model的modelPropertyWhitelist
// 如果实现了该方法,则处理过程中不会处理该列表外的属性。
NSSet *whitelist = nil;
if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {
NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist];
if (properties) {
whitelist = [NSSet setWithArray:properties];
}
}
// Get container property's generic class
// 返回容器类中的所需要存放的数据类型 (以 Class 或 Class Name 的形式)。
// 例如@{@"shadows" : [Shadow class], @"attachments" : @"Attachment"}
NSDictionary *genericMapper = nil;
if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];
if (genericMapper) {
NSMutableDictionary *tmp = [NSMutableDictionary new];
[genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (![key isKindOfClass:[NSString class]]) return;
Class meta = object_getClass(obj);
if (!meta) return;
if (class_isMetaClass(meta)) {
// @"shadows" : [Shadow class]
tmp[key] = obj;
} else if ([obj isKindOfClass:[NSString class]]) {
//@"attachments" : @"Attachment"
Class cls = NSClassFromString(obj);
if (cls) {
tmp[key] = cls;
}
}
}];
// key是modelContainerPropertyGenericClass里面的key,value是class
genericMapper = tmp;
}
}
// Create all property metas.
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;
// 获取property的meta,并且以meta.name为key,value就是meta,存入allPropertyMetas
_YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
propertyInfo:propertyInfo
generic:genericMapper[propertyInfo.name]];
if (!meta || !meta->_name) continue;
// getter和setter都要有
if (!meta->_getter || !meta->_setter) continue;
if (allPropertyMetas[meta->_name]) continue;
allPropertyMetas[meta->_name] = meta;
}
// 不断上询super,把父类的property也要加入
curClassInfo = curClassInfo.superClassInfo;
}
if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy;
// create mapper
NSMutableDictionary *mapper = [NSMutableDictionary new];
NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
// model可以实现modelCustomPropertyMapper,返回一个 Dict,将 Model 属性名对映射到 JSON 的 Key。
if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
[customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
// 从allPropertyMetas里面找key也就是property name对应的meta数据,没找到也就是这个model没有这个property或者这个property没有getter&setter
_YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
if (!propertyMeta) return;
// 找到以后从allPropertyMetas里面移除这个property name的键值对
[allPropertyMetas removeObjectForKey:propertyName];
if ([mappedToKey isKindOfClass:[NSString class]]) {
if (mappedToKey.length == 0) return;
// 设置这个property的meta数据的_mappedToKey为json里面的相应字段
propertyMeta->_mappedToKey = mappedToKey;
// 这一步支持了我们用.来分割json的嵌套
NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];
for (NSString *onePath in keyPath) {
if (onePath.length == 0) {
NSMutableArray *tmp = keyPath.mutableCopy;
[tmp removeObject:@""];
keyPath = tmp;
break;
}
}
if (keyPath.count > 1) {
// 设置这个property的meta数据的_mappedToKeyPath为一个array,存了json的keypath嵌套
propertyMeta->_mappedToKeyPath = keyPath;
[keyPathPropertyMetas addObject:propertyMeta];
}
// 如果多个property对应json里的同一个字段,会被弄成链表
// mapper以json的字段为key,value是property meta
propertyMeta->_next = mapper[mappedToKey] ?: nil;
mapper[mappedToKey] = propertyMeta;
} else if ([mappedToKey isKindOfClass:[NSArray class]]) {
// 如果一个property对应json里面的多个字段
NSMutableArray *mappedToKeyArray = [NSMutableArray new];
for (NSString *oneKey in ((NSArray *)mappedToKey)) {
if (![oneKey isKindOfClass:[NSString class]]) continue;
if (oneKey.length == 0) continue;
// 这里也是区分key path和key的,可以的话就以string的形式存入mappedToKeyArray,如果是key path就存入一个array
NSArray *keyPath = [oneKey componentsSeparatedByString:@"."];
if (keyPath.count > 1) {
[mappedToKeyArray addObject:keyPath];
} else {
[mappedToKeyArray addObject:oneKey];
}
// 设置propertyMeta的_mappedToKey和_mappedToKeyPath
if (!propertyMeta->_mappedToKey) {
propertyMeta->_mappedToKey = oneKey;
propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil;
}
}
if (!propertyMeta->_mappedToKey) return;
propertyMeta->_mappedToKeyArray = mappedToKeyArray;
// multiKeysPropertyMetas存了所有的propertyMeta
[multiKeysPropertyMetas addObject:propertyMeta];
// 链表
propertyMeta->_next = mapper[mappedToKey] ?: nil;
mapper[mappedToKey] = propertyMeta;
}
}];
}
// 遍历剩下的不custom map的property,把这些propertyMeta的_mappedToKey就设为这个property自己的name,还用mapper存propertyMeta
[allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
propertyMeta->_mappedToKey = name;
propertyMeta->_next = mapper[name] ?: nil;
mapper[name] = propertyMeta;
}];
// 把上面得到的都设置好
if (mapper.count) _mapper = mapper;
if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas;
if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas;
_classInfo = classInfo;
_keyMappedCount = _allPropertyMetas.count;
_nsType = YYClassGetNSType(cls);
// 一些在转换前后对结果的处理
_hasCustomWillTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)]);
_hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]);
_hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]);
_hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]);
return self;
}
总结一下这几个容器属性存了什么东西:
- NSDictionary *_mapper:key是json里的key字段,value是property meta
- NSArray *_allPropertyMetas:一个model的所有包括父类们的property meta array
- NSArray *_keyPathPropertyMetas:对应json里面key path的property meta array
- NSArray *_multiKeysPropertyMetas:也是存了property meta array,但是这里面的property都是一个对应多个json的keypath的
上面其实就是我们在yy_modelWithDictionary
方法里面是如何拿到了_YYModelMeta
,然后执行了下面这段:
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
也就是如果modelMeta的_hasCustomClassFromDictionary是yes,就执行class的modelCustomClassForDictionary方法,将需要解析的dict传进去,有点类似AOP的前向切入。
然后就真的开始填充啦:
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
if (!dic || dic == (id)kCFNull) return NO;
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
if (modelMeta->_keyMappedCount == 0) return NO;
// 这里也类似前向切入,把要解析的dict给modelCustomWillTransformFromDictionary函数告诉model即将开始反序列化,并允许model来处理一下dict,比如把不想序列化的键值对删掉
if (modelMeta->_hasCustomWillTransformFromDictionary) {
dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
}
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);
// 如果mapper的key数目大于等于dic的数目
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
if (modelMeta->_keyPathPropertyMetas) {
CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
if (modelMeta->_multiKeysPropertyMetas) {
CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
} else {
// 如果mapper的key数目小于dic的数目
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
// 这里是把dict传给model,让model有权决定这次的转换是不是有效
if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
这里其实最关键的就是这几个CFArrayApplyFunction是apply了什么function,干了些什么事情,所以先看下CFArrayApplyFunction是做啥的:
// Calls a function once for each element in range in an array.
// 类似给每个element执行一个方法
func CFArrayApplyFunction(_ theArray: CFArray!,
_ range: CFRange,
_ applier: ((UnsafeRawPointer?, UnsafeMutableRawPointer?) -> Void)!,
_ context: UnsafeMutableRawPointer!)
遍历容器类时,选择更高效的方法相对于 Foundation 的方法来说,CoreFoundation 的方法有更高的性能,用 CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法来遍历容器类能带来不少性能提升,但代码写起来会非常麻烦。
性能优化部分可以参考:http://www.cocoachina.com/articles/17874 写的灰常好
所以我们需要来看一下给各个元素执行的具体方法干了什么事情:
// 对象以及json dict以及model meta都包装成一个context传给了function
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);
// 给每个属性赋值
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
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;
// 一个property对应多个key
if (propertyMeta->_mappedToKeyArray) {
value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
// property对应的是keyPath,即嵌套json
} else if (propertyMeta->_mappedToKeyPath) {
value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
// 正常的就直接从dict里面取值
} else {
value = [dictionary objectForKey:propertyMeta->_mappedToKey];
}
if (value) {
// 赋值
__unsafe_unretained id model = (__bridge id)(context->model);
ModelSetValueForProperty(model, value, propertyMeta);
}
}
这里有一个蛮好的做法,把对象放入context,然后传给static的function,这样就可以获取obj啦
keyPath以及一个property对应多个key的值是通过下面的方式得到哒:
/// keypath
static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) {
id value = nil;
// 一层一层的读
for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) {
value = dic[keyPaths[i]];
if (i + 1 < max) {
if ([value isKindOfClass:[NSDictionary class]]) {
dic = value;
} else {
return nil;
}
}
}
return value;
}
/// 一个property对应多个key,就是按顺序看哪个key/keypath能取到值就break返回啦
static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) {
id value = nil;
for (NSString *key in multiKeys) {
if ([key isKindOfClass:[NSString class]]) {
value = dic[key];
if (value) break;
} else {
value = YYValueForKeyPath(dic, (NSArray *)key);
if (value) break;
}
}
return value;
}
当拿到值以后会通过ModelSetValueForProperty
将value赋值给model对象相应的property。这个函数非常的长,会做很多转换,举一段小例子吧~
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta) {
if (meta->_isCNumber) {
……
} else if (meta->_nsType) {
if (value == (id)kCFNull) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
} else {
switch (meta->_nsType) {
case YYEncodingTypeNSString:
case YYEncodingTypeNSMutableString: {
……
} break;
case YYEncodingTypeNSValue:
case YYEncodingTypeNSNumber:
case YYEncodingTypeNSDecimalNumber: {
……
} break;
case YYEncodingTypeNSData:
case YYEncodingTypeNSMutableData: {
……
} break;
// 如果property属性是NSDate
case YYEncodingTypeNSDate: {
// 如果value本来就是date就不用转了
if ([value isKindOfClass:[NSDate class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
// 如果value是string就转成date
} else if ([value isKindOfClass:[NSString class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSDateFromString(value));
}
} break;
case YYEncodingTypeNSURL: {
……
} break;
// 如果property的属性是NSArray或者mutable array
case YYEncodingTypeNSArray:
case YYEncodingTypeNSMutableArray: {
// meta->_genericCls就是这个property对应的class,之前在modelContainerPropertyGenericClass方法提供的dict里面,以property名字为key,class为value
// 如果容器的element是比较特殊的类
if (meta->_genericCls) {
NSArray *valueArr = nil;
if ([value isKindOfClass:[NSArray class]]) valueArr = value;
else if ([value isKindOfClass:[NSSet class]]) valueArr = ((NSSet *)value).allObjects;
if (valueArr) {
NSMutableArray *objectArr = [NSMutableArray new];
for (id one in valueArr) {
if ([one isKindOfClass:meta->_genericCls]) {
[objectArr addObject:one];
// 如果value element还不是这个类就转换一下
} else if ([one isKindOfClass:[NSDictionary class]]) {
Class cls = meta->_genericCls;
// 会先执行modelCustomClassForDictionary把这个element传进去,如果响应了就用返回的class,如果没有则用之前的指定class
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:one];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
// 最后用class new一个object,然后通过value给这个新的obj赋值即可
NSObject *newOne = [cls new];
[newOne yy_modelSetWithDictionary:one];
if (newOne) [objectArr addObject:newOne];
}
}
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, objectArr);
}
} else {
if ([value isKindOfClass:[NSArray class]]) {
if (meta->_nsType == YYEncodingTypeNSArray) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
((NSArray *)value).mutableCopy);
}
// 如果value是set,需要转成array或者mutable array
} else if ([value isKindOfClass:[NSSet class]]) {
if (meta->_nsType == YYEncodingTypeNSArray) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)value).allObjects);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
((NSSet *)value).allObjects.mutableCopy);
}
}
}
} break;
case YYEncodingTypeNSDictionary:
case YYEncodingTypeNSMutableDictionary: {
……
} break;
case YYEncodingTypeNSSet:
case YYEncodingTypeNSMutableSet: {
……
} // break; commented for code coverage in next line
default: break;
}
}
} else {
……
}
}
上面这一段其实就是YYmodel比较独有的自动转换,会根据property的类型把json里面拿到的value适当的做转换。
最后总结一下YYmodel的反序列化,它会给每个类建一个meta数据类,这个里面存了很多array之类的,array里面会有一些property的meta类,然后转换的时候会遍历property,从json里面去读值赋给property。整体非常简洁所以其实YYmodel的代码也不多,但是非常厉害吖~
感觉我又水了一篇。。如果你看到了这里我感到非常抱歉。。sorry 0.0
网友评论