在上文《揭秘 YYModel 的魔法(上)》 中主要剖析了 YYModel 的源码结构,而且分享了 YYClassInfo 与 NSObject+YYModel 内部有趣的实现细节。javascript
紧接上篇,本文将解读 YYModel 关于 JSON 模型转换的源码,旨在揭秘 JSON 模型自动转换魔法。html
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它易于人们阅读和编写,同时也易于机器解析和生成。它是基于 JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999 的一个子集。JSON 采用彻底独立于语言的文本格式,可是也使用了相似于 C 语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。这些特性使 JSON 成为理想的数据交换语言,点击 这里 了解更多关于 JSON 的信息。java
Model 是 面向对象编程(Object Oriented Programming,简称 OOP)程序设计思想中的对象,OOP 把对象做为程序的基本单元,一个对象包含了数据和操做数据的函数。通常咱们会根据业务需求来建立对象,在一些设计模式中(如 MVC 等)对象通常做为模型(Model),即对象建模。git
JSON 与 Model 相互转换按转换方向分为两种:github
咱们从 YYModel 的接口开始解读。编程
+ (instancetype)yy_modelWithJSON:(id)json {
// 将 json 转为字典 dic
NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
// 再经过 dic 获得 model 并返回
return [self yy_modelWithDictionary:dic];
}
复制代码
上面接口把 JSON 转 Model 很简单的分为了两个子任务:json
咱们先看一下 _yy_dictionaryWithJSON
是怎么将 json 转为 NSDictionary 的。设计模式
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
// 入参判空
if (!json || json == (id)kCFNull) return nil;
NSDictionary *dic = nil;
NSData *jsonData = nil;
// 根据 json 的类型对应操做
if ([json isKindOfClass:[NSDictionary class]]) {
// 若是是 NSDictionary 类则直接赋值
dic = json;
} else if ([json isKindOfClass:[NSString class]]) {
// 若是是 NSString 类则用 UTF-8 编码转 NSData
jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
} else if ([json isKindOfClass:[NSData class]]) {
// 若是是 NSData 则直接赋值给 jsonData
jsonData = json;
}
// jsonData 不为 nil,则表示上面的 二、3 状况中的一种
if (jsonData) {
// 利用 NSJSONSerialization 方法将 jsonData 转为 dic
dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
// 判断转换结果
if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
}
return dic;
}
复制代码
这个函数主要是根据入参的类型判断如何将其转为 NSDictionary 类型并返回。数组
其中 kCFNull
是 CoreFoundation 中 CFNull 的单例对象。如同 Foundation 框架中的 NSNull 同样,CFNull 是用来表示集合对象中的空值(不容许为 NULL)。CFNull 对象既不容许被建立也不容许被销毁,而是经过定义一个 CFNull 常量,即 kCFNull
,在须要空值时使用。xcode
官方文档: The CFNull opaque type defines a unique object used to represent null values in collection objects (which don’t allow NULL values). CFNull objects are neither created nor destroyed. Instead, a single CFNull constant object—kCFNull—is defined and is used wherever a null value is needed.
NSJSONSerialization 是用于将 JSON 和等效的 Foundation 对象之间相互转换的对象。它在 iOS 7 以及 macOS 10.9(包含 iOS 7 和 macOS 10.9)以后是线程安全的。
代码中将 NSString 转为 NSData 用到了 NSUTF8StringEncoding,其中编码类型必须属于 JSON 规范中列出的 5 种支持的编码类型:
而用于解析的最高效的编码是 UTF-8 编码,因此做者这里使用 NSUTF8StringEncoding。
官方注释: The data must be in one of the 5 supported encodings listed in the JSON specification: UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE. The data may or may not have a BOM. The most efficient encoding to use for parsing is UTF-8, so if you have a choice in encoding the data passed to this method, use UTF-8.
如今咱们要从 yy_modelWithJSON
接口中探究 yy_modelWithDictionary
是如何将 NSDictionary 转为 Model 的。
敲黑板!作好准备,这一小节介绍的代码是 YYModel 的精华哦~。
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
// 入参校验
if (!dictionary || dictionary == (id)kCFNull) return nil;
if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
// 使用当前类生成一个 _YYModelMeta 模型元类
Class cls = [self class];
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
// 这里 _hasCustomClassFromDictionary 用于标识是否须要自定义返回类
// 属于模型转换附加功能,能够不用投入太多关注
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
// 调用 yy_modelSetWithDictionary 为新建的类实例 one 赋值,赋值成功则返回 one
NSObject *one = [cls new];
// 因此这个函数中咱们应该把注意力集中在 yy_modelSetWithDictionary
if ([one yy_modelSetWithDictionary:dictionary]) return one;
return nil;
}
复制代码
代码中根据 _hasCustomClassFromDictionary
标识判断是否须要自定义返回模型的类型。这段代码属于 YYModel 的附加功能,为了避免使你们分心,这里仅作简单介绍。
若是咱们要在 JSON 转 Model 的过程当中根据状况建立不一样类型的实例,则能够在 Model 中实现接口:
+ (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
复制代码
来知足需求。当模型元初始化时会检测当前模型类是否能够响应上面的接口,若是能够响应则会把 _hasCustomClassFromDictionary
标识为 YES,因此上面才会出现这些代码:
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
复制代码
嘛~ 我以为这些附加的东西在阅读源码时很大程度上会分散咱们的注意力,此次先详细的讲解一下,之后遇到相似的代码咱们会略过,内部的实现大都与上述案例原理相同,感兴趣的同窗能够本身研究哈。
咱们应该把注意力集中在 yy_modelSetWithDictionary
上,这个函数(其实也是 NSObject+YYModel 暴露的接口)是根据字典初始化模型的实现方法。它的代码比较长,若是不想看能够跳过,在后面有解释。
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
// 入参校验
if (!dic || dic == (id)kCFNull) return NO;
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
// 根据自身类生成 _YYModelMeta 模型元类
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
// 若是模型元类键值映射数量为 0 则 return NO,表示构建失败
if (modelMeta->_keyMappedCount == 0) return NO;
// 忽略,该标识对应 modelCustomWillTransformFromDictionary 接口
if (modelMeta->_hasCustomWillTransformFromDictionary) {
// 该接口相似 modelCustomTransformFromDictionary 接口,不过是在模型转换以前调用的
dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
}
// 初始化模型设置上下文 ModelSetContext
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);
// 判断模型元键值映射数量与 JSON 所得字典的数量关系
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
// 通常状况下他们的数量相等
// 特殊状况好比有的属性元会映射字典中的多个 key
// 为字典中的每一个键值对调用 ModelSetWithDictionaryFunction
// 这句话是核心代码,通常状况下就是靠 ModelSetWithDictionaryFunction 经过字典设置模型
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
// 判断模型中是否存在映射 keyPath 的属性元
if (modelMeta->_keyPathPropertyMetas) {
// 为每一个映射 keyPath 的属性元执行 ModelSetWithPropertyMetaArrayFunction
CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
// 判断模型中是否存在映射多个 key 的属性元
if (modelMeta->_multiKeysPropertyMetas) {
// 为每一个映射多个 key 的属性元执行 ModelSetWithPropertyMetaArrayFunction
CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
} else { // 模型元键值映射数量少,则认为不存在映射多个 key 的属性元
// 直接为每一个 modelMeta 属性元执行 ModelSetWithPropertyMetaArrayFunction
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
// 忽略,该标识对应接口 modelCustomTransformFromDictionary
if (modelMeta->_hasCustomTransformFromDictionary) {
// 该接口用于当默认 JSON 转 Model 不适合模型对象时作额外的逻辑处理
// 咱们也能够用这个接口来验证模型转换的结果
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
复制代码
代码已经注明必要中文注释,关于两处自定义扩展接口咱们再也不多说,因为代码比较长咱们先来梳理一下 yy_modelSetWithDictionary
主要作了哪些事?
ModelSetContext
ModelSetWithDictionaryFunction
模型设置上下文 ModelSetContext
其实就是一个包含模型元,模型实例以及待转换字典的结构体。
typedef struct {
void *modelMeta; ///< 模型元
void *model; ///< 模型实例,指向输出的模型
void *dictionary; ///< 待转换字典
} ModelSetContext;
复制代码
你们确定都注意到了 ModelSetWithDictionaryFunction
函数,不论走哪条逻辑分支,最后都是调用这个函数把字典的 key(keypath)对应的 value 取出并赋值给 Model 的,那么咱们就来看看这个函数的实现。
// 字典键值对建模
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
// 拿到入参上下文
ModelSetContext *context = _context;
// 取出上下文中模型元
__unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
// 根据入参 _key 从模型元中取出映射表对应的属性元
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
// 拿到待赋值模型
__unsafe_unretained id model = (__bridge id)(context->model);
// 遍历 propertyMeta,直到 propertyMeta->_next == nil
while (propertyMeta) {
// 当前遍历的 propertyMeta 有 setter 方法,则调用 ModelSetValueForProperty 赋值
if (propertyMeta->_setter) {
// 核心方法,拎出来说
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
}
propertyMeta = propertyMeta->_next;
};
}
复制代码
ModelSetWithDictionaryFunction
函数的实现逻辑就是先经过模型设置上下文拿到带赋值模型,以后遍历当前的属性元(直到 propertyMeta->_next == nil),找到 setter
不为空的属性元经过 ModelSetValueForProperty
方法赋值。
ModelSetValueForProperty
函数是为模型中的属性赋值的实现方法,也是整个 YYModel 的核心代码。别紧张,这个函数写得很友好的,也就 300 多行而已 😜(可有可无的内容我会尽可能忽略掉),不过忽略的太多会影响代码阅读的连续性,若是嫌长能够不看,文章后面会总结一下这个函数的实现逻辑。
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta) {
// 若是属性是一个 CNumber,即输入 int、uint……
if (meta->_isCNumber) {
// 转为 NSNumber 以后赋值
NSNumber *num = YYNSNumberCreateFromID(value);
// 这里 ModelSetNumberToProperty 封装了给属性元赋值 NSNumber 的操做
ModelSetNumberToProperty(model, num, meta);
if (num) [num class]; // hold the number
} else if (meta->_nsType) {
// 若是属性属于 nsType,即 NSString、NSNumber……
if (value == (id)kCFNull) { // 为空,则赋值 nil(经过属性元 _setter 方法使用 objc_msgSend 将 nil 赋值)
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
} else { // 不为空
switch (meta->_nsType) {
// NSString 或 NSMutableString
case YYEncodingTypeNSString:
case YYEncodingTypeNSMutableString: {
// 处理可能的 value 类型:NSString,NSNumber,NSData,NSURL,NSAttributedString
// 对应的分支就是把 value 转为 NSString 或者 NSMutableString,以后调用 setter 赋值
...
} break;
// NSValue,NSNumber 或 NSDecimalNumber
case YYEncodingTypeNSValue:
case YYEncodingTypeNSNumber:
case YYEncodingTypeNSDecimalNumber: {
// 对属性元的类型分状况赋值(中间可能会涉及到类型之间的转换)
...
} break;
// NSData 或 NSMutableData
case YYEncodingTypeNSData:
case YYEncodingTypeNSMutableData: {
// 对属性元的类型分状况赋值(中间可能会涉及到类型之间的转换)
...
} break;
// NSDate
case YYEncodingTypeNSDate: {
// 考虑可能的 value 类型:NSDate 或 NSString
// 转换为 NSDate 以后赋值
...
} break;
// NSURL
case YYEncodingTypeNSURL: {
// 考虑可能的 value 类型:NSURL 或 NSString
// 转换为 NSDate 以后赋值(这里对 NSString 的长度判断是否赋值 nil)
...
} break;
// NSArray 或 NSMutableArray
case YYEncodingTypeNSArray:
case YYEncodingTypeNSMutableArray: {
// 对属性元的泛型判断
if (meta->_genericCls) { // 若是存在泛型
NSArray *valueArr = nil;
// value 所属 NSArray 则直接赋值,若是所属 NSSet 类则转为 NSArray
if ([value isKindOfClass:[NSArray class]]) valueArr = value;
else if ([value isKindOfClass:[NSSet class]]) valueArr = ((NSSet *)value).allObjects;
// 遍历刚才经过 value 转换来的 valueArr
if (valueArr) {
NSMutableArray *objectArr = [NSMutableArray new];
for (id one in valueArr) {
// 遇到 valueArr 中的元素属于泛型类,直接加入 objectArr
if ([one isKindOfClass:meta->_genericCls]) {
[objectArr addObject:one];
} else if ([one isKindOfClass:[NSDictionary class]]) {
// 遇到 valueArr 中的元素是字典类,
Class cls = meta->_genericCls;
// 忽略
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:one];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
// 还记得咱们直接的起点 yy_modelSetWithDictionary,将字典转模型
// 我以为这应该算是一个间接递归调用
// 若是设计出的模型是无限递归(从前有座山,山上有座庙的故事),那么确定会慢
NSObject *newOne = [cls new];
[newOne yy_modelSetWithDictionary:one];
// 转化成功机也加入 objectArr
if (newOne) [objectArr addObject:newOne];
}
}
// 最后将获得的 objectArr 赋值给属性
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, objectArr);
}
} else {
// 没有泛型,嘛~ 判断一下 value 的可能所属类型 NSArray 或 NSSet
// 转换赋值(涉及 mutable)
...
}
} break;
// NSDictionary 或 NSMutableDictionary
case YYEncodingTypeNSDictionary:
case YYEncodingTypeNSMutableDictionary: {
// 跟上面数组的处理超类似,泛型的间接递归以及无泛型的类型转换(mutable 的处理)
...
} break;
// NSSet 或 NSMutableSet
case YYEncodingTypeNSSet:
case YYEncodingTypeNSMutableSet: {
// 跟上面数组的处理超类似,泛型的间接递归以及无泛型的类型转换(mutable 的处理)
...
}
default: break;
}
}
} else { // 属性元不属于 CNumber 和 nsType
BOOL isNull = (value == (id)kCFNull);
switch (meta->_type & YYEncodingTypeMask) {
// id
case YYEncodingTypeObject: {
if (isNull) { // 空,赋值 nil
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
} else if ([value isKindOfClass:meta->_cls] || !meta->_cls) {
// 属性元与 value 从属于同一个类,则直接赋值
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value);
} else if ([value isKindOfClass:[NSDictionary class]]) {
// 嘛~ value 从属于
NSObject *one = nil;
// 若是属性元有 getter 方法,则经过 getter 获取到实例
if (meta->_getter) {
one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
}
if (one) {
// 用 yy_modelSetWithDictionary 输出化属性实例对象
[one yy_modelSetWithDictionary:value];
} else {
Class cls = meta->_cls;
// 略过
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:value];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
// 用 yy_modelSetWithDictionary 输出化属性实例对象,赋值
one = [cls new];
[one yy_modelSetWithDictionary:value];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
}
}
} break;
// Class
case YYEncodingTypeClass: {
if (isNull) { // 空,赋值(Class)NULL,因为 Class 实际上是 C 语言定义的结构体,因此使用 NULL
// 关于 nil,Nil,NULL,NSNull,kCFNull 的横向比较,我会单独拎出来在下面介绍
((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL);
} else {
// 判断 value 可能的类型 NSString 或判断 class_isMetaClass(object_getClass(value))
// 若是知足条件则赋值
...
}
} break;
// SEL
case YYEncodingTypeSEL: {
// 判空,赋值(SEL)NULL
// 不然转换类型 SEL sel = NSSelectorFromString(value); 而后赋值
...
} break;
// block
case YYEncodingTypeBlock: {
// 判空,赋值(void (^)())NULL
// 不然判断类型 [value isKindOfClass:YYNSBlockClass()] 以后赋值
...
} break;
// struct、union、char[n],关于 union 共同体感兴趣的同窗能够本身 google,这里简单介绍一下
// union 共同体,相似 struct 的存在,可是 union 每一个成员会用同一个存储空间,只能存储最后一个成员的信息
case YYEncodingTypeStruct:
case YYEncodingTypeUnion:
case YYEncodingTypeCArray: {
if ([value isKindOfClass:[NSValue class]]) {
// 涉及 Type Encodings
const char *valueType = ((NSValue *)value).objCType;
const char *metaType = meta->_info.typeEncoding.UTF8String;
// 比较 valueType 与 metaType 是否相同,相同(strcmp(a, b) 返回 0)则赋值
if (valueType && metaType && strcmp(valueType, metaType) == 0) {
[model setValue:value forKey:meta->_name];
}
}
} break;
// void* 或 char*
case YYEncodingTypePointer:
case YYEncodingTypeCString: {
if (isNull) { // 判空,赋值(void *)NULL
((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL);
} else if ([value isKindOfClass:[NSValue class]]) {
// 涉及 Type Encodings
NSValue *nsValue = value;
if (nsValue.objCType && strcmp(nsValue.objCType, "^v") == 0) {
((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, nsValue.pointerValue);
}
}
}
default: break;
}
}
}
复制代码
额 😓 我是真的已经忽略掉不少代码了,没办法仍是有点长。其实代码逻辑仍是很简单的,只是模型赋值涉及的编码类型等琐碎逻辑比较多致使代码量比较大,咱们一块儿来总结一下核心代码的实现逻辑。
ModelSetNumberToProperty
赋值嘛~ 其实上面的代码除了长之外逻辑仍是很简单的,总结起来就是根据可能出现的类型去作出对应的逻辑操做,建议各位有时间仍是去读下源码,尤为是本身项目中用到 YYModel 的同窗。相信看完以后会对 YYModel 属性赋值一清二楚,这样在使用 YYModel 的平常中出现任何问题均可以心中有数,改起代码天然若有神助哈。
额...考虑到 NSDictionary to Model 的整个过程代码量不小,我花了一些时间将其逻辑总结概括为一张图:
但愿能够尽本身的努力让文章的表述变得更直白。
相比于 JSON to Model 来讲,Model to JSON 更简单一些。其中由于 NSJSONSerialization 在对 JSON 的转换时作了一些规定:
Note: 上文出自 NSJSONSerialization 官方文档。
知道了这一点后,咱们就能够从 YYModel 的 Model to JSON 接口 yy_modelToJSONObject
处开始解读源码了。
- (id)yy_modelToJSONObject {
// 递归转换模型到 JSON
id jsonObject = ModelToJSONObjectRecursive(self);
if ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject;
if ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject;
return nil;
}
复制代码
嘛~ 一共 4 行代码,只须要关注一下第一行代码中的 ModelToJSONObjectRecursive
方法,Objective-C
的语言特性决定了从函数名称便可无需注释看懂代码,这个方法从名字上就能够 get 到它是经过递归方法使 Model 转换为 JSON 的。
// 递归转换模型到 JSON,若是转换异常则返回 nil
static id ModelToJSONObjectRecursive(NSObject *model) {
// 判空或者能够直接返回的对象,则直接返回
if (!model || model == (id)kCFNull) return model;
if ([model isKindOfClass:[NSString class]]) return model;
if ([model isKindOfClass:[NSNumber class]]) return model;
// 若是 model 从属于 NSDictionary
if ([model isKindOfClass:[NSDictionary class]]) {
// 若是能够直接转换为 JSON 数据,则返回
if ([NSJSONSerialization isValidJSONObject:model]) return model;
NSMutableDictionary *newDic = [NSMutableDictionary new];
// 遍历 model 的 key 和 value
[((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description;
if (!stringKey) return;
// 递归解析 value
id jsonObj = ModelToJSONObjectRecursive(obj);
if (!jsonObj) jsonObj = (id)kCFNull;
newDic[stringKey] = jsonObj;
}];
return newDic;
}
// 若是 model 从属于 NSSet
if ([model isKindOfClass:[NSSet class]]) {
// 若是可以直接转换 JSON 对象,则直接返回
// 不然遍历,按须要递归解析
...
}
if ([model isKindOfClass:[NSArray class]]) {
// 若是可以直接转换 JSON 对象,则直接返回
// 不然遍历,按须要递归解析
...
}
// 对 NSURL, NSAttributedString, NSDate, NSData 作相应处理
if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString;
if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string;
if ([model isKindOfClass:[NSDate class]]) return [YYISODateFormatter() stringFromDate:(id)model];
if ([model isKindOfClass:[NSData class]]) return nil;
// 用 [model class] 初始化一个模型元
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:[model class]];
// 若是映射表为空,则不作解析直接返回 nil
if (!modelMeta || modelMeta->_keyMappedCount == 0) return nil;
// 性能优化细节,使用 __unsafe_unretained 来避免在下面遍历 block 中直接使用 result 指针形成的没必要要 retain 与 release 开销
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:64];
__unsafe_unretained NSMutableDictionary *dic = result;
// 遍历模型元属性映射字典
[modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
// 若是遍历当前属性元没有 getter 方法,跳过
if (!propertyMeta->_getter) return;
id value = nil;
// 若是属性元属于 CNumber,即其 type 是 int、float、double 之类的
if (propertyMeta->_isCNumber) {
// 从属性中利用 getter 方法获得对应的值
value = ModelCreateNumberFromProperty(model, propertyMeta);
} else if (propertyMeta->_nsType) { // 属性元属于 nsType,即 NSString 之类
// 利用 getter 方法拿到 value
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
// 对拿到的 value 递归解析
value = ModelToJSONObjectRecursive(v);
} else {
// 根据属性元的 type 作相应处理
switch (propertyMeta->_type & YYEncodingTypeMask) {
// id,须要递归解析,若是解析失败则返回 nil
case YYEncodingTypeObject: {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = ModelToJSONObjectRecursive(v);
if (value == (id)kCFNull) value = nil;
} break;
// Class,转 NSString,返回 Class 名称
case YYEncodingTypeClass: {
Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromClass(v) : nil;
} break;
// SEL,转 NSString,返回给定 SEL 的字符串表现形式
case YYEncodingTypeSEL: {
SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromSelector(v) : nil;
} break;
default: break;
}
}
// 若是 value 仍是没能解析,则跳过
if (!value) return;
// 当前属性元是 KeyPath 映射,即 a.b.c 之类
if (propertyMeta->_mappedToKeyPath) {
NSMutableDictionary *superDic = dic;
NSMutableDictionary *subDic = nil;
// _mappedToKeyPath 是 a.b.c 根据 '.' 拆分红的字符串数组,遍历 _mappedToKeyPath
for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) {
NSString *key = propertyMeta->_mappedToKeyPath[i];
// 遍历到结尾
if (i + 1 == max) {
// 若是结尾的 key 为 nil,则使用 value 赋值
if (!superDic[key]) superDic[key] = value;
break;
}
// 用 subDic 拿到当前 key 对应的值
subDic = superDic[key];
// 若是 subDic 存在
if (subDic) {
// 若是 subDic 从属于 NSDictionary
if ([subDic isKindOfClass:[NSDictionary class]]) {
// 将 subDic 的 mutable 版本赋值给 superDic[key]
subDic = subDic.mutableCopy;
superDic[key] = subDic;
} else {
break;
}
} else {
// 将 NSMutableDictionary 赋值给 superDic[key]
// 注意这里使用 subDic 间接赋值是有缘由的,缘由就在下面
subDic = [NSMutableDictionary new];
superDic[key] = subDic;
}
// superDic 指向 subDic,这样在遍历 _mappedToKeyPath 时便可逐层解析
// 这就是上面先把 subDic 转为 NSMutableDictionary 的缘由
superDic = subDic;
subDic = nil;
}
} else {
// 若是不是 KeyPath 则检测 dic[propertyMeta->_mappedToKey],若是为 nil 则赋值 value
if (!dic[propertyMeta->_mappedToKey]) {
dic[propertyMeta->_mappedToKey] = value;
}
}
}];
// 忽略,对应 modelCustomTransformToDictionary 接口
if (modelMeta->_hasCustomTransformToDictionary) {
// 用于在默认的 Model 转 JSON 过程不适合当前 Model 类型时提供自定义额外过程
// 也能够用这个方法来验证转换结果
BOOL suc = [((id<YYModel>)model) modelCustomTransformToDictionary:dic];
if (!suc) return nil;
}
return result;
}
复制代码
额...代码仍是有些长,不过相比于以前 JSON to Model 方向上由 yy_modelSetWithDictionary
,ModelSetWithDictionaryFunction
和 ModelSetValueForProperty
三个方法构成的间接递归来讲算是很是简单了,那么总结一下上面的代码逻辑。
Note: 这里有一个性能优化的细节,用
__unsafe_unretained
修饰的 dic 指向咱们最后要 return 的 NSMutableDictionary *result,看做者的注释:// avoid retain and release in block
是为了不直接使用result
在后面遍历映射表的代码块中没必要要的 retain 和 release 操做以节省开销。
文章写的比较用心(是我我的的原创文章,转载请注明 lision.me/),若是发现错误会优先在个人 我的博客 中更新。
若是对文章有哪些意见能够直接在个人微博 @Lision 联系我(由于社区发文以后的通知太多了,因此我把这些 push 给关了只留了微博,微博冷清~~~~(>_<)~~~~)。
补充~ 我建了一个技术交流微信群,想在里面认识更多的朋友!若是各位同窗对文章有什么疑问或者工做之中遇到一些小问题均可以在群里找到我或者其余群友交流讨论,期待你的加入哟~
Emmmmm..因为微信群人数过百致使不能够扫码入群,因此请扫描上面的二维码关注公众号进群。