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<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组成的字典
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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容