YYModel 源码导读

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 *ivarInfos; ///< ivars  
//此处是 name:nameInfo,birthday:birthdayInfo组成的字典信息

NSDictionary *methodInfos; ///< methods
//比如name的 set,get方法。birthday的set,get方法等。毕竟@property是会自动生成 set,get 方法的。

NSDictionary *propertyInfos; ///< properties
//name:namePropertyInfo, birthday:brithdayPropertyInfo组成的字典
YYModel 源码导读_第1张图片
Snip20170215_7.png

至于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之类的。难度不大,有空可能会更新。
  大家哪里不懂,问问我呗,也让我看看哪个点没讲清楚。

源码:https://github.com/PomTTcat/YYModelGuideRead_JEFF

你可能感兴趣的:(YYModel 源码导读)