本篇文章是YYModel源码阅读的第二篇文章, 主要是对NSObject+YYModel中源码阅读做的笔记.
YYModel主要提供json转model的接口, 内部是利用YYClassInfo封装好的接口实现对json数据的解析.
分析方面:
- YYModel属性和接口信息
- YYModelPropertyMeta
- YYModelMeta
- 模型转换
- 格式转换 类型编码
- 性能优化点
YYModel属性接口信息
NSObject+YYModel文件中提供了NSObject``NSArray``NSDictionary的分类, 提供了json转模型的方法, 无侵入性, 在YYModel中提供了YYModel协议, 进行自定义操作.
YYModel协议:
// 当json中的key和属性的propertyName不一致的时候, 需要使用这个方法, 进行匹配操作
/*
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"name" : @"n",
@"page" : @"p",
@"desc" : @"ext.desc", // keyPath
@"bookID": @[@"id", @"ID", @"book_id"]}; // mutiKeys
}
*/
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
// 用于设置集合内部元素类型, 例如设置数组中model的class
/*
@property NSArray *shadows;
@property NSSet *borders;
@property NSDictionary *attachments;
--------------------------
+ (NSDictionary *)modelContainerPropertyGenericClass {
return @{@"shadows" : [YYShadow class],
@"borders" : YYBorder.class,
@"attachments" : @"YYAttachment" };
}
*/
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
// 根据json中的字段不同, 可以将json解析成不同的class
+ (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
// 黑名单: 黑名单中的属性将不会被解析, nil表示忽略
+ (nullable NSArray<NSString *> *)modelPropertyBlacklist;
// 白名单: 如果属性不在白名单中, 属性将不会被解析, nil表示忽略
+ (nullable NSArray<NSString *> *)modelPropertyWhitelist;
// 在json转换之前调用, 可以对json进行修改
- (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic;
...
YYModelMeta
YYModelMeta主要存储类型相关信息, 内部是对YYClassInfo的封装, 在YYModelMeta内部记录着所有的属性(YYModelPropertyMeta), keyPath对应的YYModelPropertyMeta, 以及multiKeys对应的YYModelPropertyMeta, NSType类类型等信息.
/// 模型类信息
@interface _YYModelMeta : NSObject {
@package
// runtime类信息
YYClassInfo *_classInfo;
/// Key:mapped key and key path, Value:_YYModelPropertyMeta.
NSDictionary *_mapper;
/// Array<_YYModelPropertyMeta>, all property meta of this model.
/// array<YYModelPropertyMeta> 当前模型的所有propertyMeta数组
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.
/// 被映射到多个key的_YYModelPropertyMeta数组
NSArray *_multiKeysPropertyMetas;
/// The number of mapped key (and key path), same to _mapper.count.
/// 映射key的数量, =_mapper.count
NSUInteger _keyMappedCount;
/// Model class type. 模型cls类型
YYEncodingNSType _nsType;
BOOL _hasCustomWillTransformFromDictionary;
BOOL _hasCustomTransformFromDictionary;
BOOL _hasCustomTransformToDictionary;
BOOL _hasCustomClassFromDictionary;
}
@end
在YYModelMeta的类构造方法中, 使用static CFMutableDictionaryRef cache以类型cls作为key将元类对象做了全局内存缓存, 避免在下次使用时重复创建(类的元数据中存放的是类的相关信息, 不会改变)
+ (instancetype)metaWithClass:(Class)cls {
// 类元数据的缓存
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;
}
YYModelPropertyMeta
@interface _YYModelPropertyMeta : NSObject {
@package
NSString *_name; ///< property's name, 属性名称
YYEncodingType _type; ///< property's type, 属性类型, OC类型解析为YYEncodingTypeObject
YYEncodingNSType _nsType; ///< property's Foundation type, 属性在Foundation框架中的类型
BOOL _isCNumber; ///< is c number type, 是否是CNumber
Class _cls; ///< property's class, or nil, 属性类
Class _genericCls; ///< container's generic class, or nil if threr's no generic class,如果是容器类型, 就是容器内元素的类型(NSArray中存放的item类型)
SEL _getter; ///< getter, or nil if the instances cannot respond, getter方法
SEL _setter; ///< setter, or nil if the instances cannot respond, setter方法
BOOL _isKVCCompatible; ///< YES if it can access with key-value coding, 是否支持KVC
BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver, 结构体是否支持归档/解档
BOOL _hasCustomClassFromDictionary; ///< class/generic class implements +modelCustomClassForDictionary:, 是否实现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, 映射key json中的key
NSArray *_mappedToKeyPath; ///< the key path mapped to (nil if the name is not key path), 映射keyPath
NSArray *_mappedToKeyArray; ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys), 映射的key/keyPath数组, 如果没有就为nil
YYClassPropertyInfo *_info; ///< property's info, 属性信息
_YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key. 如果多个属性映射到同一个key, next就指向下一个模型属性元
}
@end
在YYModelPropertyMeta中已经包含了YYClassPropertyInfo信息, YYClassPropertyInfo中已经包含了getter和setter方法, 为什么还要在YYModelPropertyMeta中提供成员变量_getter和_setter呢?
查看源码在为model的属性赋值的时候, 使用的是objc_msgSend, 作者在iOS JSON 模型转换库评测](blog.ibireme.com/2015/10/23/…)中提到使用getter和setter方法替代KVC在性能上会有很大提升, 直接操作成员变量相比getter和setter方法又有较大的提升.
json to model
json转model主要是使用yy_modelSetWithDictionary:(NSDictionary *)di:方法
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
// 容错处理 kCFNull NSNull
if (!dic || dic == (id)kCFNull) return NO;
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
// 根据自身生成YYModelMeta模型属性, 所有key的处理已经在YYModelMeta的初始化方法中处理完成
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
if (modelMeta->_keyMappedCount == 0) return NO;
// 转换之前对json进行自定义处理
if (modelMeta->_hasCustomWillTransformFromDictionary) {
dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
}
/*
typedef struct {
void *modelMeta; ///< _YYModelMeta
void *model; ///< id (self)
void *dictionary; ///< NSDictionary (json)
} ModelSetContext;
*/
// 设置模型上下文
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);
// 如果modelMeta个数大于json中的key个数 为什么做这个判断呢?
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
// 字典转模型
// typedef void (*CFDictionaryApplierFunction)(const void *key, const void *value, void *context);
// 遍历字典, 并且每遍历一次执行一次函数
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 {
// 属性元对应字典中的多个key
// 自定义的key值无效?
// typedef void (*CFArrayApplierFunction)(const void *value, void *context);
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
// 上下文
ModelSetContext *context = _context;
// 类元数据
__unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
// 从类元数据的mapper中通过key取出对应的propertyMeta
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
// model
__unsafe_unretained id model = (__bridge id)(context->model);
// 根据value类型
// model set propertyMeta.setter value
while (propertyMeta) {
// 调用setter方法 赋值
if (propertyMeta->_setter) {
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
}
propertyMeta = propertyMeta->_next;
};
}
ModelSetWithDictionaryFunction中是通过key-value键值对, 获取元类YYModelMeta对应key的propertyMeta, 将value数据通过setter方法赋值给属性, ModelSetValueForProperty方法中主要对value类型做了处理, 根据属性类型将value转换为属性对应类型.
YYModel中提升性能的优化
- 将类的元数据在内存中做缓存, YYModelMeta中使用单例将类的元数据存放到单例中.
- 在为属性赋值的时候, 效率: 成员变量 > getter setter > objc_msgSend > KVC
- 遍历字典和数组时 使用C函数, 提高效率, CFDictionaryApplyFunction和CFArrayApplyFunction
- 使用存C函数和内联函数代替OC方法, OC方法调用会带来消息发送的开销, 如果C函数较小, 使用内联函数可以避免压栈出栈的操作.
- ARC内存管理, 使用__unsafe_unretained
- 在转换之前Model的属性个数和JSON中键的个数是确定的, 选择循环较少的方式遍历. 源码中将modelMeta->_keyMappedCount和dic的键个数做对比, 当属性个数较多是, 选择dic进行遍历. 当属性个数较少时, 遍历属性.
思考
- hash
- 什么类型不能使用KVC?
- NSType哪些类型
- CNumber类型
- 类型之间的转换 string->date
- 获取NSBlock类型的技巧
- 遍历字典和数组的性能 方法