YYModel 是一个把 Json 数据转换成 model 的一个轻量级工具。本文将深入源码来谈谈YYModel是如何实现 Json->Model 的。建议读者有运行时的基础。
运行时是什么?这里简单概括一下。
运行时:可以通过 runtime 的方法,在程序运行的时候,获取一个对象的所有信息。对象拥有的属性,成员变量,对象可以响应的方法。以及父类的这些信息,一直到根类 NSObject。
从最简单的+ (instancetype)modelWithJSON:(id)json;
说起。
大步骤有两步:
1.Json->Dict
2.Dict->Model
第一步Json->Dict
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json{...}
这一步看源码比较简单,不赘述。就是把 json转成 OC 中的字典。
第二步Dict->Model
第二步主要里面分成2步
2.1 通过运行时获取 对象模型的类信息
2.2 用类的元信息来处理 dict->model
我们来看看有效代码
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary {
//依据 实例对象所属类 生成 meta。 meta 是一个含有大量 cls 信息的对象。
Class cls = [self class];
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
//这一步可以暂时忽略,等所有的看完了,这个就会懂了。
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
//dict->model
NSObject *one = [cls new];
if ([one modelSetWithDictionary:dictionary]) return one;
}
2.1获取类的元信息。
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
我们先进到[_YYModelMeta metaWithClass:cls]
里去。
我们直接看有效代码,YYModel 有很多巧妙的东西,到时候我另开一篇讲解。
+ (instancetype)metaWithClass:(Class)cls {
_YYModelMeta meta = [[_YYModelMeta alloc] initWithClass:cls];
return meta;
}
🙄没错,你们看到一大坨代码,其实可以简单地描述成这么一句。略微有细节缺失。但重点就这一句。
进到_YYModelMeta meta = [[_YYModelMeta alloc] initWithClass:cls];
里面看看。我把白名单,黑名单,自定义映射的功能暂时去掉了。那些是对字典转模型的补充。我们先搞懂原理,再去扩展开来。
- (instancetype)initWithClass:(Class)cls {
//依据 cls 获取YYClassInfo
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
// Create all property metas.
NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
YYClassInfo *curClassInfo = classInfo;
//这里发生了非常美妙的递归。判定条件,子类和父类都存在。然后去遍历子类,遍历完子类。把当前类设置为 之前遍历类的 父类。指导,curClassInfo = NSObject
while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy)
for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) {
_YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
propertyInfo:propertyInfo
generic:genericMapper[propertyInfo.name]];
allPropertyMetas[meta->_name] = meta;
//这里通过递归给所以的属性添加
}
curClassInfo = curClassInfo.superClassInfo;
}
//创建映射
[allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
propertyMeta->_mappedToKey = name;
mapper[name] = propertyMeta; //添加到字典里面
}];
//name = "<_YYModelPropertyMeta: 0x1700e8780>";
//birthday = "<_YYModelPropertyMeta: 0x1700e8780>";
return self;
}
上面的代码总共分三步。
1.根据类生成YYClassInfo
2.根据YYClassInfo去遍历类,父类的属性。目的:生成所有属性的_YYModelPropertyMeta。
放到集合allPropertyMetas里面。
3.遍历allPropertyMetas,生成常规的映射字典mapper。
allPropertyMetas是数组,能不能用来映射需要去遍历确认。
有些name:namePropertyMetas是以 keyPath,multiKeys存在的。
并不直接生成常规 mapper。这块不理解可以先放放。
简单地说最终产生了所有属性的 key:keyPropertyMetas
YYClassInfo是什么?上面的第一步就产生了这个。
@interface _YYModelMeta : NSObject {
YYClassInfo *_classInfo;
......
}
YYClassInfo这个类可以理解成 实例对象 的类信息都存在里面了。举个例子。YYAuthor是一个继承自NSObject的类。
@interface YYAuthor : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSDate *birthday;
@end
YYAuthor *author = [YYAuthor New];
把author传进来,会产生如下信息。
Class cls; ///< 模型是什么类. 此处是YYAuthor
Class superCls; ///< super class object 此处是NSObject
Class metaCls; ///YYAuthor是 metaCls的实例对象。就像author是YYAuthor的实例对象。
BOOL isMeta; ///< 当前类是否是元类。
NSString *name; ///< class name 此处是字符串YYAuthor
YYClassInfo *superClassInfo; ///< 父类的类信息。此处是 NSObject的信息
NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars
//此处是 name:nameInfo,birthday:birthdayInfo组成的字典信息
NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods
//比如name的 set,get方法。birthday的set,get方法等。毕竟@property是会自动生成 set,get 方法的。
NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties
//name:namePropertyInfo, birthday:brithdayPropertyInfo组成的字典
至于YYClassInfo是如何产生的每个细节。那真的是太繁琐了。不过我 Github 上的代码都做了注释。有兴趣的可以看一下。对于一个 OC 对象是如何组成的,可以有非常深刻的理解。(连接在文末)
2.2 用类的元信息来处理 dict->model
这一步反而格外简单。
- (BOOL)modelSetWithDictionary:(NSDictionary *)dic {
//依据 self 获得之前生成的_YYModelMeta。
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
//把参数塞进结构体。
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta); //_YYModelMeta
context.model = (__bridge void *)(self); //self //这个好像并没有用起来
context.dictionary = (__bridge void *)(dic); //oc字典。 //这个值传入 context 在 json 少于map的情况下使用
//遍历Dict
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
}
1.获取生成之前的_YYModelMeta
2.生成结构体
3.遍历字典
重点是ModelSetWithDictionaryFunction
这个方法相当于遍历字典的 block。
OK,我们点进方法,然后提取一下。
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
if (propertyMeta->_setter) {
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
}
propertyMeta = propertyMeta->_next;
};
}
属性的 setter方法是否存在。存在则调用ModelSetValueForProperty。
ModelSetValueForProperty总结一下就是用objc_msgSend给 模型 发送setter消息 参数为 value。完成赋值!
YYModel 字典转模型最基本的框架就是这样的。我这里再总结一下!下面这个是简单版本。
1.二进制Json -> OC Dictionary
2.OC Dictionary -> Model
2.1 通过运行时获取类的元信息(_YYModelMeta)
2.1.1 类的元信息需要获取YYClassInfo
2.1.1.1 YYClassInfo获取
2.1.1.1.1 class相关,父类,元类指针等
2.1.1.1.1 YYClassMethods
2.1.1.1.2 YYClassProperties
2.1.1.1.3 YYClassIvarInfos
2.1.2 根据YYClassInfo去遍历类,父类的属性。
2.1.2.1 依据属性生成_YYModelPropertyMeta
2.1.2.2 把_YYModelPropertyMeta添加到allPropertyMetas
2.1.3 遍历_YYModelPropertyMeta,生成常规的 mapper(这是一个dict)
2.2 使用类的元信息发生字典转模型
2.2.1 获取之前生成的 _YYModelMeta
2.2.2 生成结构体ModelSetContext context
2.2.3 遍历 OC Dictionary,context传参
2.2.3.1 以字典的 key 为 key 去尝试获取对应的 _YYModelPropertyMeta
2.2.3.2 如果有改_YYModelPropertyMeta,并且有setter 方法。
2.2.3.2.1 依据propertyMeta,
用objc_msgSend给对象发送 setter 方法。value是之前字典的 value。
接下来的是复杂版本的。更加全面!
1.二进制Json -> OC Dictionary
2.OC Dictionary -> Model
2.1 通过运行时获取类的元信息(_YYModelMeta)
2.1.1 类的元信息需要获取YYClassInfo
2.1.1.1 YYClassInfo获取
2.1.1.1.1 class相关,父类,元类指针等
2.1.1.1.1 YYClassMethods
2.1.1.1.2 YYClassProperties
2.1.1.1.3 YYClassIvarInfos
2.1.2 获取黑名单
2.1.3 获取白名单
2.1.4 获取自定义容易类型
2.1.5 根据YYClassInfo去遍历类,父类的属性。
2.1.5.1 该属性在黑名单中则 跳出遍历
2.1.5.2 假如有白名单则必须在白名单中 不然跳出遍历
2.1.5.3 依据属性生成_YYModelPropertyMeta
2.1.5.4 判定并把_YYModelPropertyMeta添加到allPropertyMetas
2.1.6 如果完成了自定义映射。
2.1.6.1 在局部allPropertyMetas中移除该属性名
2.1.6.2 依据不同的映射情况,填充_mappedToKey,_mappedToKeyPath,
_mappedToKeyArray。并添加到总 Mapper 里。
2.1.7 遍历allPropertyMetas(此时比之前少了自定义的映射)
2.1.7.1 简单的依据 name ,添加到 Mapper 中。
2.1.8 把大量局部变量赋值到全局变量。立一些 flag。
2.2 多态:此处可以根据OC Dictionary更改当前的类。一般在用来指向子类。大部分情况不用。
2.3 使用类的元信息发生字典转模型
2.3.1 获取之前生成的 _YYModelMeta
2.3.2 生成结构体ModelSetContext context
2.3.3 如果 模型属性数量 大于 字典内数量(那个少遍历哪个)
2.3.3.1 遍历 OC Dictionary,context传参
2.3.3.1 以字典的 key 为 key 去尝试获取对应的 _YYModelPropertyMeta
2.3.3.2 如果有改_YYModelPropertyMeta,并且有setter 方法。
2.3.3.2.1 依据propertyMeta,
用objc_msgSend给对象发送 setter 方法。value是之前字典的 value。
2.3.3.2 如果_keyPathPropertyMetas有值,遍历其数组
2.3.3.3 如果_multiKeysPropertyMetas有值,遍历其数组
2.3.4 遍历模型属性,数组遍历。
2.3.5 在模型转换最后被调用,自己手动完成一些值的赋值,类似自定义映射
本篇文章只是梳理了字典转模型的框架,细节可以在下面这个项目中查看。
我给 YYModel 做了一定程度的注释。会有一些细节的缺失,我会持续更新的。
至于 Model转字典和运行时copy,nscoding之类的。难度不大,有空可能会更新。
大家哪里不懂,问问我呗,也让我看看哪个点没讲清楚。