1.YYModel
首先YYModel是以类别的方式对NSObject、NSArray、NSDictionary进行的扩展,这样的直接好处就是对你自己的工程代码入侵性小,非常好接入,并且有一个YYModel的协议用来满足你自己的定制需求。
2._YYModelMeta 和 _YYModelPropertyMeta
先看两个很重要的类
_YYModelMeta:
/// 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
解释下这些属性:
- YYClassInfo *_classInfo;
Model类的YYClassInfo,构造_YYModelMeta的时候同时构造生成。(关于YYClassInfo的详细,请看上一篇) - NSDictionary *_mapper;
核心索引,要把源json转为Model就要靠此map把属性一一对应。 - _mapper的key是Model的属性名称,如果Model有定义modelCustomPropertyMapper,则有映射关系的属性在_mapper中的key是映射的那个值。
- _mapper的value是对应属性的_YYModelPropertyMeta,_YYModelPropertyMeta对该属性有详细的描述,以保证在json和model的属性对应关系成立及转换时赋值成功等。_YYModelPropertyMeta的介绍,在下面说。
- _mapper的key是已经通过黑白名单过滤的。
- NSArray *_allPropertyMetas;
可以理解为 _mapper.allValues - NSArray *_keyPathPropertyMetas;
在Model的modelCustomPropertyMapper映射时,可能会有路径映射,这样的propertyMetas会存放在这个数组中 - NSArray *_multiKeysPropertyMetas;
在Model的modelCustomPropertyMapper映射时,可能会有一个属性对应json源数据的多个字段,这样的propertyMetas会存放在这个数组中 - NSUInteger _keyMappedCount;
_keyMappedCount = _allPropertyMetas.count - YYEncodingNSType _nsType;
Model类的类型
再看下_YYModelMeta的些实现:
-
_YYModelMeta是有缓存的,每次读取对应Model的_YYModelMeta时会根据是否有缓存或是否有更新来决定取缓存或生成一个新的meta
/// 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 = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); lock = dispatch_semaphore_create(1); }); dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls)); dispatch_semaphore_signal(lock); if (!meta || meta->_classInfo.needUpdate) { meta = [[_YYModelMeta alloc] initWithClass:cls]; 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; }
创建流程
其实很简单,看代码即懂:获取到该Model的YYClassInfo
获取到该Model的容器属性存放的对象的类的map
遍历YYClassInfo.propertyInfos来创建对应的存放_YYModelPropertyMeta数组
创建对应的mapper
给_YYModelMeta属性赋值
_YYModelPropertyMeta:
/// A property info in object model.
@interface _YYModelPropertyMeta : NSObject {
@package
NSString *_name; ///< property's name
YYEncodingType _type; ///< property's type
YYEncodingNSType _nsType; ///< property's Foundation type
BOOL _isCNumber; ///< is c number type
Class _cls; ///< property's class, or nil
Class _genericCls; ///< container's generic class, or nil if threr's no generic class
SEL _getter; ///< getter, or nil if the instances cannot respond
SEL _setter; ///< setter, or nil if the instances cannot respond
BOOL _isKVCCompatible; ///< YES if it can access with key-value coding
BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver
BOOL _hasCustomClassFromDictionary; ///< class/generic class implements +modelCustomClassForDictionary:
/*
property->key: _mappedToKey:key _mappedToKeyPath:nil _mappedToKeyArray:nil
property->keyPath: _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
property->keys: _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath _mappedToKeyArray:keys(array)
*/
NSString *_mappedToKey; ///< the key mapped to
NSArray *_mappedToKeyPath; ///< the key path mapped to (nil if the name is not key path)
NSArray *_mappedToKeyArray; ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys)
YYClassPropertyInfo *_info; ///< property's info
_YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key.
}
@end
其实大部分属性看一眼名字或作者的注释就知道什么意思什么用了,就不做解释了,重点解释几个一眼看不出来具体什么作用的解释:
-
BOOL _isKVCCompatible;
标记该属性能否支持 key-value 模式,代码中是这样,一看便知:switch (meta->_type & YYEncodingTypeMask) { case YYEncodingTypeBool: case YYEncodingTypeInt8: case YYEncodingTypeUInt8: case YYEncodingTypeInt16: case YYEncodingTypeUInt16: case YYEncodingTypeInt32: case YYEncodingTypeUInt32: case YYEncodingTypeInt64: case YYEncodingTypeUInt64: case YYEncodingTypeFloat: case YYEncodingTypeDouble: case YYEncodingTypeObject: case YYEncodingTypeClass: case YYEncodingTypeBlock: case YYEncodingTypeStruct: case YYEncodingTypeUnion: { meta->_isKVCCompatible = YES; } break; default: break; }
-
BOOL _isStructAvailableForKeyedArchiver;
是否支持归档,支持的是如下的:dispatch_once(&onceToken, ^{ NSMutableSet *set = [NSMutableSet new]; // 32 bit [set addObject:@"{CGSize=ff}"]; [set addObject:@"{CGPoint=ff}"]; [set addObject:@"{CGRect={CGPoint=ff}{CGSize=ff}}"]; [set addObject:@"{CGAffineTransform=ffffff}"]; [set addObject:@"{UIEdgeInsets=ffff}"]; [set addObject:@"{UIOffset=ff}"]; // 64 bit [set addObject:@"{CGSize=dd}"]; [set addObject:@"{CGPoint=dd}"]; [set addObject:@"{CGRect={CGPoint=dd}{CGSize=dd}}"]; [set addObject:@"{CGAffineTransform=dddddd}"]; [set addObject:@"{UIEdgeInsets=dddd}"]; [set addObject:@"{UIOffset=dd}"]; types = set; }); if ([types containsObject:propertyInfo.typeEncoding]) { meta->_isStructAvailableForKeyedArchiver = YES; }
BOOL _hasCustomClassFromDictionary;
是否有自定义的属性映射,主要看该属性所属的Model类中是否有modelCustomPropertyMapper_YYModelPropertyMeta *_next;
在映射的类型中,会出现多个属性对应json中同一个字段的情况,而这里的_next就会指向和该属性同一个映射字段的下一个属性_YYModelPropertyMeta,形成一个链表样式的结构。这样在给某个属性赋值时,就可以同时把同一个映射字段的属性全部赋值,增加性能。
3.几个私有方法
ModelSetValueForProperty
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta)
这个是用于给Model属性赋值的时候,代码太长就不贴了,逻辑也很简单,一句话说一下就是:
根据meta的类型,然后用 objc_msgSend 方法去动态调用 meta->setter 去把value赋值给属性ModelSetWithDictionaryFunction
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
ModelSetContext *context = _context;
__unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
__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赋值,用字典的key找到对应的_YYModelMeta对象,用value作为赋的值,执行ModelSetValueForProperty, 并且同时对相同映射的属性赋值-
ModelSetWithPropertyMetaArrayFunction
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;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); } }
用meta来给model赋值,这里比用字典来赋值多了一点东西就是,他会根据meta的映射类型,去取出对应的value再去赋值。
4.一般的YYModel工作流程
我们用的最多的就是 yy_modelWithJSON :
+ (instancetype)yy_modelWithJSON:(id)json {
NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
return [self yy_modelWithDictionary:dic];
}
_yy_dictionaryWithJSON 主要是把传进来的json参数转化为字典类型,然后供后边使用
然后看 yy_modelWithDictionary :
+ (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];
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
NSObject *one = [cls new];
if ([one yy_modelSetWithDictionary:dictionary]) return one;
return nil;
}
这里在拿到_YYModelMeta之后,判断了下modelMeta->_hasCustomClassFromDictionary,这个是看是否需要在json到model对象转换中创建不同类的实例。
然后最主要看转换过程 yy_modelSetWithDictionary,具体看注释:
- (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)]; //获取到对应的modelMeta,这里有完整的映射关系,和属性meta描述
if (modelMeta->_keyMappedCount == 0) return NO; //如果map里没有映射关系,返回NO
//模型转换的前处理,看使用者是否需要在模型转换前对dic做一些啥特殊处理,返回处理后的dic
if (modelMeta->_hasCustomWillTransformFromDictionary) {
dic = [((id)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);
//转换流程
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
//如果model的属性数量,大于等于json源字段的数量,说明model中可能存在多个属性对应一个json源字段的或者有无法和json源字段对应的
//让dic里的元素全部执行一遍ModelSetWithDictionaryFunction,即赋值流程
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
//如果model里有路径映射的,则都执行一遍ModelSetWithPropertyMetaArrayFunction,对路径映射的属性赋值
if (modelMeta->_keyPathPropertyMetas) {
CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
//如果model里有多属性映射的的,则都执行一遍ModelSetWithPropertyMetaArrayFunction
if (modelMeta->_multiKeysPropertyMetas) {
CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
} else {
//如果model的属性数量小于json源字段的数量,那只需要找到model属性对应的json字段就可以,则用ModelSetWithPropertyMetaArrayFunction转换
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
//转换后处理,看使用者是否需要些额外处理
if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
这就完成了转换,完美~~~