前言
YYModel 是一个iOS JSON模型转化库,和其他一些同类型库相比,具有比较好的性能优势。本文会对YYModel的源码进行分析,具体用法作者ibireme在github中有提及。YYModel的目录结构很简单,只有两个类, NSObject+YYModel
和 YYClassInfo
。YYClassInfo
主要对根类NSObject 的 Ivar
, Method
, Property
以及Class
本身进行了封装,NSObject+YYModel
是 NSObject的分类,扩展了一些JSON模型转化的方法。
YYClassInfo
要点
YYClassIvarInfo : 对 Class的Ivar进行了封装
YYClassMethodInfo : 对 Class的Method进行了封装
YYClassPropertyInfo : 对 Class的Property进行了封装
YYClassInfo : 对Class进行了封装,包含了YYClassIvarInfo,YYClassMethodInfo,YYClassPropertyInfo
变量类型编码
说到变量,可能我们写代码中最常见的就是变量了。当我们自定义一个类时,首先总是会申明一些属性,而且每个属性都会带有一些修饰词。比如是否原子性,内存管理原则,只读性。这些都可以通过这个属性的property_getAttributes
方法获取,苹果为所有的类型,包括属性类型都有编码,具体可以查看苹果官方文档:类型编码, 苹果官方文档:属性类型。 下面是一个简单例子:
- 申明属性
|
|
2.通过runtime 来获取所有属性
|
|
3.打印结果
|
|
我们可以看到属性的所有类型编码信息,其中第一个代表是这个变量的类型,以T开头,最后一个代表的是变量的名字,一般用V_属性名表示,中间的部分就是我们声明的修饰符。比如age的类型是 Tq,而在官方文档中q 代表了A long long
,64bit下NSInteger的取值范围就是long == long long ,N代表了非原子性,变量名是_age。其他的@代表了OC类型 id
,cat类型即是T@”Cat”,&代表了 这个变量是retain (ARC下strong相当于retain),C 代表了copy
YYEncodingType
根据类型编码自定义了类型枚举,包含了三个部分
YYEncodingTypeMask : 0~8位的值,变量的数据类型
YYEncodingTypeQualifierMask : 8~16位的值,变量的方法类型
YYEncodingTypePropertyMask: 16~24位的值,变量的属性类型
这里把枚举值分成三个部分,通过 枚举值 & 对应 Mask 取出对应的变量类型,区分不同类型部分。YYEncodingGetType
是根据变量的数据类型编码值获取自定义YYEncodingType
Var Method Property
YYClassIvarInfo:用于存取变量的信息
|
|
Ivar是表示实例变量的类型,其实际是一个指向objc_ivar结构体的指针,其定义如下:
|
|
运用runtime,name
通过的ivar_getName
获取,offset
通过ivar_getOffset
获取,typeEncoding
通过 ivar_getTypeEncoding
获取,type
通过自定义方法 YYEncodingGetType
获取。其中offset
是变量的基地址偏移量,可以通过它来直接访问变量数据,下面是例子:
|
|
打印结果:
|
|
YYClassMethodInfo:用于存取方法的信息
|
|
Method
的信息同 Ivar
一样,通过runtime的 method相关方法获取,其他的一些信息同Ivar
,主要来说一下 SEL
和 Imp
SEL: 方法ID,C字符串
typedef struct objc_selector *SEL;
|
|
IMP:方法函数指针
OC是动态语言,方法调用(也叫做消息发送)是在运行时动态绑定的,而非编译时。如何做到正确的调用指定的方法呢?这里就需要用到SEL和IMP。编译器会将消息发送转换成对objc_msgSend(void /* id self, SEL op, ... */ )
方法的调用 。objc_msgSend
方法根据对象的isa指针找到对象的类,通过在类中的调度表(dispatch table)中查找SEL 获得 IMP,精确执行指定方法。
YYClassPropertyInfo: 用于存取属性的信息
|
|
YYClassInfo: 对Class的封装,包含了上面三部分的信息
|
|
元类
其中有metaCls
代表了元类。那什么是元类呢?下面是一张经典的类结构图
在OC中,每个实例对象都有一个isa指针,它指向了对象的class。而这个class也同样有一个isa指针,它就是指向了它的元类,其实类也是一个对象,所以对象之于类的关系,就相当于类(类对象)之于其元类(类对象的类)的关系。那元类有什么用呢?我们都知道在OC中调用方法有实例方法和类方法。我们调用实例方法,就是通过isa指针找到指定的class,查找存储在class中的方法列表执行方法,所以元类的作用就是调用类方法时,通过查找保存在元类中的类方法执行方法的作用。那为什么不把所有方法都保存在类中,可能这样更加高效也节省资源吧,具体可以自己查找资料。
在YYClassInfo中,有一个_update
方法,用来更新类中存储的信息。
初始化方法
|
|
要点:
CFDictionaryCreateMutable 和 CFDictionarySetValue 不是线程安全的,所以需要创建锁,采用
dispatch_semaphore
通过控制信号量实现了锁设置缓存,如果在缓存中存在class,则直接获取到对应的ivar,method,property,否者创建YYClassInfo实例对象
NSObject+YYModel:
NSObject+YYModel
是YYModel的核心类,主要部分:
强制内联C函数:功能函数
私有类_YYModelPropertyMeta : 管理Model属性的数据, 类型, 映射的key,keyPath
私有类 _YYModelMeta :管理Model 数据,类型,存储 映射key,keypath,_YYModelPropertyMeta
NSObject NSArray NSDictionary (YYModel) : 几个分类,YYModel主体功能实现
YYModel 协议:扩展功能实现
私有类_YYModelPropertyMeta
_YYModelPropertyMeta
是对上面的 YYClassPropertyInfo
的进一步封装。
内部实例变量
|
|
主要是最后几个变量。其中_mappedToKey
_mappedToKeyPath
_mappedToKeyArray
是属性映射的key,keyPath ,key (keypath) 数组_mappedToKeyArray
中可以是key和keyPath,实际取其中第一个映射到的值
一般一个属性名对应一个key值,如果多个属性名对应同一个key,这里就需要next发挥作用了。比如
|
|
首先name1最先得到映射,对mapKey进行赋值,取得json中的name字段进行赋值一系列操作,此时next指针为nil
name2接着进行映射,对mapKey进行赋值,接着取得原来json key对应的属性描述对象,将name2的next指针,指向name1。
name3接着进行映射,对mapKey进行赋值,接着取得原来json key对应的属性描述对象,将name3的next指针,指向name2。
代码中的实现
|
|
初始化方法
|
|
私有类_YYModelMeta
_YYModelMeta
是对 YYClassInfo
的再次封装
内部变量
|
|
初始化
|
|
首先从缓存中加载,没有在根据传入cls 创建meta,并做缓存处理, dispatch_semaphore 确保线程安全
|
|
JSON 转 Model
|
|
传入的json可以是 NSDictionary
, NSString
, NSData
,_yy_dictionaryWithJSON
统一转化成字典
|
|
结构体 ModelSetContext
存储 modelMeta ,model, dic 作为 CFDictionaryApplyFunction
和 CFArrayApplyFunction
的context 参数,传递数据
调用CoreFoundation 的 CFDictionaryApplyFunction 和 CFArrayApplyFunction 回调自定义的 Apply functionstatic void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context)
和 static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context)
根据获得的value ,model ,_propertyMeta 最后统一调用 下面方法,运用runtime的 objc_msgSend
设置model属性
|
|
自定义的CFDictionaryApplyFunction
的回调方法,CoreFoundation中的原回调函数typedef void (*CFDictionaryApplierFunction)(const void *key, const void *value, void *context);
自定义的CFArrayApplyFunction
的回调方法,CoreFoundation中的原回调函数typedef void (*CFArrayApplierFunction)(const void *value, void *context);
最终实现model 属性赋值。 该方法比较长,首先对 meta的属性类型进行判断,主要分为三类,
- C基本数据类型
- Foundation 类型
- 其他类型,如 id, Class ,block,SEL等等
根据类型获取对应value,
最后都调用 ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value)
进行model 属性赋值
|
|
Model 转 JSON
其中有效的JSON Object 只能是以下类型:
NSArray,NSDictionary,NSString,NSNumber,NSNull。
1.如果是NSDictionary,NSSet,NSArray 类型,递归调用此方法获得JSON Object
2.如果是NSURL,NSAttributedString ,NSDate, NSData,做简单相应返回
3.根据Model类型创建modelMeta,取实例变量_mapper
获取所有属性名和`propertyMeta
,再根据propertyMeta
的 类型_type
获得相应的value
, 根据有无_mappedToKeyPath
再进一步处理,赋值,最后 判断有无自定义_hasCustomTransformToDictionary
,返回最终转化结果
主要方法: