上一篇中《从YYModel源码中可以学到什么:后篇》中主要学习了YYModel
的源码结构,只是分享了YYModel
整体结构。
承接上篇,本文将解读YYModel
如何进行JSON模型转换的,接下来一起揭开YYModel
的神秘面纱吧!
目录
- JSON -> Model
- Model -> JSON
JSON转Model
首先来看JSON是如何转为Model。查看YYModel
的接口,提供了一个方法:
1
| + (instancetype)yy_modelWithJSON:(id)json;
|
注意json
为id
类型,接收三种不同类型参数NSString
,NSData
,NSDictionay
。下面是内部实现:
1 2 3 4
| + (instancetype)yy_modelWithJSON:(id)json { NSDictionary *dic = [self _yy_dictionaryWithJSON:json]; return [self yy_modelWithDictionary:dic]; }
|
方法中调用了一个私有方法_yy_dictionaryWithJSON:
,该方法将id
类型的json
(NSDictionary, NSString, NSData)转为字典。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| + (NSDictionary *)_yy_dictionaryWithJSON:(id)json { // 验证json if (!json || json == (id)kCFNull) return nil; // 两个临时变量 NSDictionary *dic = nil; NSData *jsonData = nil; // 根据json类型,进行相应处理 if ([json isKindOfClass:[NSDictionary class]]) { dic = json; } else if ([json isKindOfClass:[NSString class]]) { jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding]; } else if ([json isKindOfClass:[NSData class]]) { jsonData = json; } // 使用NSJSONSerialization将Data转为字典 if (jsonData) { dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL]; if (![dic isKindOfClass:[NSDictionary class]]) dic = nil; } return dic; }
|
该方法很简单,就是判断id
类型是指(NSDictionary,NSString,NSData)中的哪一个,分别处理。
在json == (id)kCFNull
中kCFNull
是什么意思?这条语句起到什么作用?
nil
: 指向OC中对象的空指针
Nil
: 指向OC中类的空指针
NULL
:定义其他类型(基本类型、C类型)的空指针
NSNull
:集合对象中,表示空值的对象。如给数组设置空值,使用NSNull
,而不能使用nil
。
kCFNull
是NSNull
的单例。
if (!json || json == (id)kCFNull) return nil;
该判断的意思是,json对象不存在,或者为空是返回nil
。
获取到字典后,调用字典转Model方法:
1
| + (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary
|
此方法也是在.h
文件中暴露出来的方法。接下来查看具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| /** 字典转model
@param dictionary 字典 @return 返回Model对象 */ + (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary { // 1. 验证,空值,nil和是否是字典 if (!dictionary || dictionary == (id)kCFNull) return nil; if (![dictionary isKindOfClass:[NSDictionary class]]) return nil; Class cls = [self class]; // 2. 创建一个YYModelMeta对象 _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]; // 判断是否需要自定义返回模型的类型,这是YYModel协议中的内容,算是附加功能暂时先忽略,后面在介绍。 if (modelMeta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:dictionary] ?: cls; } // 3. 创建model对象 NSObject *one = [cls new]; // 4. 关键方法 if ([one yy_modelSetWithDictionary:dictionary]) return one; return nil; }
|
这个方法主要的任务是调用了yy_modelSetWithDictionary:
方法。这个方法也是在.h
文件中暴露的,它的作用是根据字典初始化模型。代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| - (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic { // 1. 值和类型验证 if (!dic || dic == (id)kCFNull) return NO; if (![dic isKindOfClass:[NSDictionary class]]) return NO; // 2. 创建Modelmeta对象 _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)]; // 属性个数,如为零返回。创建失败 if (modelMeta->_keyMappedCount == 0) return NO; // YYModel协议,暂且忽略 if (modelMeta->_hasCustomWillTransformFromDictionary) { dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic]; if (![dic isKindOfClass:[NSDictionary class]]) return NO; } // 3. 创建结构体 ModelSetContext context = {0}; context.modelMeta = (__bridge void *)(modelMeta); context.model = (__bridge void *)(self); context.dictionary = (__bridge void *)(dic); // 模型元值数量和字典数量关系 // 1. 通常情况是两者相等, // 2. 模型元键值少于字典个数 if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) { // 调用ModelSetWithDictionaryFunction方法,这是核心方法。 // 参数: // 1. 要操作的字典 // 2. 为每个键值对调用一次的回调函数 // 3. 指针大小的程序定义的值,作为第三个参数传递给应用程序函数,但此函数未使用该值. 与参数2适应 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) { // 每个keypath都执行ModelSetWithPropertyMetaArrayFunction CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } } else { // 每个keypath都执行ModelSetWithPropertyMetaArrayFunction CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas, CFRangeMake(0, modelMeta->_keyMappedCount), ModelSetWithPropertyMetaArrayFunction, &context); } // 忽略 if (modelMeta->_hasCustomTransformFromDictionary) { return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic]; } return YES; }
|
上面注释该方法大致干了几件事。
- 验证参数(为了程序健壮性一定要这么做)
- 创建
YYModelMeta
对象
- 创建
ModelSetContext
结构体
- 为字典的每个键值对调用
ModelSetWithDictionaryFunction
方法
- 验证结果
关于ModelSetContext
是一个结构体。包含模型元,模型实例和待处理字典。
1 2 3 4 5
| typedef struct { void *modelMeta; ///< _YYModelMeta void *model; ///< id (self) void *dictionary; ///< NSDictionary (json) } ModelSetContext;
|
通过上面的分析,线路越来越清晰,下面看一下核心方法ModelSetWithDictionaryFunction
将字典的键值对取出赋值给Model。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| // 获取到字典的键值对,和上下文信息。 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); // 核心内容,遍历所有的属性元。知道_next = nil while (propertyMeta) { if (propertyMeta->_setter) { // 最终转换(高潮) ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta); } propertyMeta = propertyMeta->_next; }; }
|
该方法主要任务是,遍历所有的属性元,调用模型属性赋值方法ModelSetValueForProperty
。
最终实现。也就是YYModel
的最最最核心的部分。不过,这个方法长的离谱。由于涉及到较多的编码类型,需要对不同的类型区分处理,导致代码过长。
由于ModelSetValueForPorperty
代码较长,这里不再复制代码。只梳理一下实现的逻辑,注释代码在Github自行查阅。
- 元类型,
isCNumber
, nsType
和其他类型来区分处理。
isCNumber
此时调用方法ModelSetNumberToPorperty
。该方法将NSNumber类型的值根据不同的编码赋值给属性。
nsType
是YYEncodingNSType
枚举类型,枚举Foundation
所有的类型,对不同的类型进行处理。
- 最后是除上述的其他情况。
YYEncodingType
枚举定义的情况。
调用ModelSetNumberToPorperty
方法,该方法作用是:识别null
、bool
、123.23
、"123.45"
等类型,转为NSNumber。
以上就是JSON转Model全部过程。
Model转JSON
相对JSON
转Model
来说,Model
转JSON
就简单多了。可以使用NSJSONSerialization
类将Foundation对象转为JSON。但是转为JSON必须满足一下条件。
- 顶层对象必须是
NSArray
或NSDictionary
- 所有对象都是实例
NSString
,NSNumber
,NSArray
,NSDictionary
或者NSNull
- 所有字典键都是实例
NSString
- 数字不是
NAN
或无穷大
官方说明NSJSONSerialization文档
接下来看看是如何转换的,首先看到的方法是:
1
| - (id)yy_modelToJSONObject;
|
该方法的时下很简单只有几行代码。
1 2 3 4 5 6 7 8
| - (id)yy_modelToJSONObject { // 关键方法 id jsonObject = ModelToJSONObjectRecursive(self); if ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject; if ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject; return nil; }
|
内部调用了方法ModelToJSONObjectRecursive
方法。作者对该方法注释中说,返回一个有效的JSON
对象(NSArray/NSDictionary/NSString/NSumber/NSNull)
或者为空。查看源码,该方法做了几件事
- 验证参数
- 根据参数类型进行处理,NSString, NSNumber直接返回数据;字典,集合,数组类型,需要遍历所有元素递归处理;最后处理对象类型。
- 返回不同类型的处理结果
总结
承接上文,本文对JSON转Model主线路进行详细的分析,关于一些辅助功能没有分析。还有,一些代码比较长,由于篇幅限制,所以注释代码放在《GitHub》。
由于笔者能力有限,不可避免的出现错误,欢迎大家指正。