YYModel 是一个高性能的 json model 解析库。
使用
Model 与 JSON 转换
1 | // JSON: |
Model 属性名和 JSON 中 key 不相同
- model 属性对应于 JSON 中较深的 keypath
- model 属性对应多个 JSON 中的 key
实现 JSON 转 Model 映射方法 modelCustomPropertyMapper
1 | // JSON: |
Model 中包含其他 Model
直接使用自定义的类:
1 | // JSON |
容器类属性
数组和字典中存放的数据可以通过 modelContainerPropertyGenericClass
指定:
1 | @class Shadow, Border, Attachment; |
JSON 转 Model 完成后的操作
在 class 中实现相应方法,通过 dic
可以拿到 JSON 的字典,self
就是转换好的 Model:
1 | - (id)modelCustomTransformFromDictionary:(NSDictionary *)dic { |
官方文档上没有提及这个方法,只是在我看源码的时候发现的。我觉得这个方法还是比较有用的,相当于在获取数据后在 Model 内执行类似于 watch 方法,可以把一些简单的数据处理放在 model 层。
额外功能:Coding/Copying/hash/equal/description 方法的实现
1 | @interface YYShadow :NSObject <NSCoding, NSCopying> |
源码解析
文件结构
YYModel 的核心是以下两个文件:
YYClassInfo
主要是对 OC 中成员变量、属性、方法和类的一层封装。将一些需要通过 Runtime 方法获取到的属性保存在对象中,有助于减少相关方法调用此时,提高效率。NSObject+YYModel
提供了方法实现 model json 转换。
YYClassInfo
YYClassInfo 中包含四个类,分别是对类及类中的成员标量、属性和方法的封装。
YYClassIvarInfo
YYClassIvarInfo
的属性及初始化方法如下:
1 | @interface YYClassIvarInfo : NSObject |
可以看到初始化方法就是把 ivar
的几个成员变量通过 Runtime 方法取出来,赋值给 YYClassIvarInfo
的相应属性中。
YYClassMethodInfo
1 | @interface YYClassMethodInfo : NSObject |
这里就不再列举它的初始化方法了,总之就是把方法的相关信息提取到对象中。
YYClassPropertyInfo
属性中多了几个字段,包含属性的类,属性类遵守的协议以及属性的 get set 方法
1 | @interface YYClassPropertyInfo : NSObject |
YYClassInfo
1 | @interface YYClassInfo : NSObject |
类封装中属性多了一些,包含了父类的 YYClassInfo
对象,以及属性、方法及成员变量的数组。
当然 YYClassInfo
中还有一些其他的细节,不是很重要就不在这里详述了。只要知道以上四个封装类即可。
NSObject+YYModel
这个文件中实现了 Model和 JSON 的相互转换。不过这里先不直接开始转换方法的分析,而是先从与其相关的两个类 _YYModelPropertyMeta
以及 _YYModelMeta
开始。
_YYModelPropertyMeta
_YYModelPropertyMeta
是 YYClassPropertyInfo
的再上一层的封装。如果说 YYClassPropertyInfo
只是 property_t
的简单封装,那么 _YYModelPropertyMeta
就包含了 JSON 转 Model 所需的一些自定义配置。
1 | @interface _YYModelPropertyMeta : NSObject { |
_YYModelMeta
_YYModelMeta 的属性
_YYModelMeta
是 YYClassInfo
的上层封装。属性如下:
1 | @interface _YYModelMeta : NSObject { |
获取 _YYModelMeta 的方法
通过下面的方法获取 _YYModelMeta
。_YYModelMeta
是类级的对象,一个类型可以共享一个实例。因此,_YYModelMeta
实例被保存在一个单例的 cache 字典中,只有该类型未被初始化的时候才会通过 initWithClass
方法创建:
1 | + (instancetype)metaWithClass:(Class)cls { |
创建 _YYModelMeta
_YYModelMeta
的初始化方法比较长,这里截取一段段代码分别分析。
容器类型的设置
1 | // 将 modelContainerPropertyGenericClass 方法返回的泛型 |
这个方法用来处理容器类属性。当 Model 实现了 modelContainerPropertyGenericClass
方法的时候(如果不知道,可以看上面的使用介绍),就将结果暂时保存在 genericMapper
中,待到后面实例化 _YYModelPropertyMeta
对象的时候设置到 class genericCls
中。
Model 对应 JSON keyPaths 的设置
相关实现是通过在 Model 中实现 modelCustomPropertyMapper
实现的。如果 modelCustomPropertyMapper
返回的字典中值是一个带有 .
的字符串,那么就说明 Model 对应 JSON 中的 keyPaths:
1 | // 在 model 中实现了 modelCustomPropertyMapper 的情况 |
Model 对应 JSON 中多个可能的字段的设置
还是 modelCustomPropertyMapper
中实现的。如果返回的字典中的值是一个数组,那么说明 Model 中的属性可能对应 JSON 中多个可能的字段:
1 | if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) { |
JSON → Model 的实现
外部调用方法
上面把用到的数据结构都介绍了一遍,现在开始正真的 JSON → Model 的转换。这里省略了 JSON → NSDictionary 的转换(通过 NSJSONSerialization
实现)。直接看 NSDictionary → Model 的实现:
1 | + (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary { |
可以看到,上面的设置过程主要涉及了两个方法。其中第一个是 JSON 转 Modal 的核心:
- ModelSetWithDictionaryFunction
- ModelSetWithPropertyMetaArrayFunction
ModelSetWithDictionaryFunction
这个方法传入的三个参数分别为,JSON 转成的 Dictionary 的每个键值对和传入的上下文。
1 | static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) { |
最核心的方法就是 ModelSetValueForProperty
,真正将值赋值给 model 就是在这个方法中。它的处理代码非常长,因为针对不同类型的属性要做不同的处理。一般情况下,可以将处理代码分为三类:
- c 中的数字类型。如 int 等
- oc 中的对象类型。如 NSString,NSNumber 等
- 除以上两种的其他类型。如 block 类型,SEL 类型
C 类型设置
C 类型值设置方法如下:
1 | if (meta->_isCNumber) { |
先把 Value 转为 NSNumber 类型,然后调用 ModelSetNumberToProperty
方法。摘取一部分代码如下:
1 | static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model, |
可以看到,最终是直接调用了 objc_msgSend
执行 model 相应属性的 setter 方法。
OC 类型设置
1 | if (meta->_nsType) { |
这里截取的代码有点长。不过已经是精简了很多的状态了。总体而言还是判断属性的类型,然后调用相应的 objc_msgSend
方法。
Model → JSONObject 的实现
Model 到 JSONObject 的转换也就是 Model 到数组或者字典这种模型的转换,核心方法在递归方法 ModelToJSONObjectRecursive
。
1 | - (id)yy_modelToJSONObject { |
具体的转换代码非常长。做了注释后还是贴上来了:
1 | // 递归转换模型到 JSON,如果转换异常则返回 nil |
总的来说,还是判断传入的 Model 的类型,如果是 NSString 这种基本类型,那么就直接返回了。如果是 class 类型,那么就生成 class 对应的 _YYModelMeta
对象,然后遍历其中的所有属性,将其赋值给 result
这个字典中。
一些点
设值
一般情况下,我们都会认为字典模型转换都是使用 KVC 的方式。然鹅 YYModel 并不是使用 KVC,而是通过 objc_msgSend
调用 Model 中 property 的 setter 方法。这样速度上更快。
内存优化
__unsafe_unretained
在作用上和 __weak
类似,但是 __unsafe_unretained
会更易造成野指针。
访问具有 __weak
属性的变量时,实际上会调用 objc_loadWeak()
和 objc_storeWeak()
来完成,这也会带来很大的开销,所以如果你能确定在该变量的使用过程中,不会被回收,就可以使用 __unsafe_unretained
替代 __weak
。
使用 c 函数
YYModel 中使用了大量的 c 函数。使用纯 C 函数可以避免 ObjC 的消息发送带来的开销。
使用内联函数
内联函数用 inline
标记。编译时,类似宏替换,使用函数体替换调用处的函数名。
优势是内联减少了函数调用的开销,在调用时不发生控制转移。劣势是如果调用次数很多时,一般会增加了代码量。
内联函数和宏定义相比差别在于:
- 编译阶段:宏定义使用预处理器preprocessor实现,只是预处理符号表的简单替换。内联函数则在编译阶段进行替换,会有类型检查和参数有效性的检测。
- 安全性:内联函数具有类型检查与参数有效性的验证,而宏没有;
总结
总的来说,YYModel 为了效率使用了很多 c 函数,其实在日常的编程中是不太能用到的(或者说,你要这么写是会被骂的),但正是这些更底层不常用的函数使 YYModel 成为了一个高效的 JSON Model 转换库。