Mantle 源码解读

Mantle 的类层次关系图:

Mantle 的类层次关系图
  • CustomModel 类,就是我们自定义的 Model,在 MVC 或者 MVVM 开发模式上必不可少的 Model 类,如:User, Note, Tage 等。

  • 基类MTLModel 提供了一些默认的行为来处理对象的初始化和归档操作,同时可以获取到对象所有属性的键值集合。主要是两件事:

    • 将dictionary 键值对,映射成该类的所有属性(名字为 key)的值;
    • 将类的所有属性作为 key,属性值作为 value,转换成dictionary;

MTLJSONSerializing 该协议可以让用户自定义哪些JSON字段,映射成 CustomModel 的哪些属性,名称不一致的时候,极为有用。

  • MTLJSONAdaptor 起到一个适配器的作用,将 JSON Dict 进行验证,将其键和值,通过 CustomModel 上的属性transformer,将 JSON的 value 转换成定义的需要的类型 value, 比如将时间戳 long 类型,转换成 NSDate 类型,构建一个 Dictionary,传入给 MTLModel构造方法,完成 JSON 到 model 的转换;同理,通过 model 类型的 dictionary, 通过 model 反向 transformer 将 model 属性值,转换成 JSON value后,构建一个 JSON Dict。这个 JSON Dict 就是我们需要通过网络发送给服务器的结构。

可以看到 Mantle 核心的内容就是两个类,我们主要分析这两个类的一些核心方法。

先分析一下MTLModel 类,从最上层的函数开始,一步一步深入分析:

MTLModel init方法

MTLModel 通过这个初始化方法,从 dictionary,初始化一个 Model 对象。将 dictionary 上的 key 与 value,通过 KVC 机制,将 model 的属性进行初始化,遍历整个 dictionary 都没有发生错误时,则返回初始化对象。如果在 KVC 验证过程中发生错误,则直接返回 nil,终止循环。在 MTLValidateAndSetValue 方法的第四个参数,forceUpdate,传入 YES,表示验证通过后,强制设置值,方法体的内容如下:

如此就完成了 Dictionary 转换成 Model 类。

MTLModel 定义了一个 dictionaryValue 属性,并定义了 getter 方法。目的是完成 model 转换为 dictionary:

将 transitory 属性键与 permanent 属性键相加,然后通过 KVC 获取这些键的值,组成一个 dictionary 作为返回值。

从关联对象上获取这些,如果为 nil,调用 generateAndCacheStorageBehaviors 方法生成。

generateAndCacheStorageBehaviors

这个方法,分四步:

  1. 遍历所有属性。

  2. 根据属性的存储类型,进行分类。

  3. 分好类,分别添加 至对应的集合,并且设置关联对象,使用copy 模式。

  4. 循环遍历属性,调用 block 进行设置。

第1步遍历所有属性的方法为:propertyKeys 判断其拷贝行为,过滤掉不做存储的属性,需要注意的是它同时会对 hash 、 superclass 、 description 、 debugDescription 这四个属性进行判断,在 NSObject 内这四个都是 readonly 的,如果你不去将它设为 readwrite 的话,它们是不做存储的。

这里再讲一下Mantle的存储行为。

MTLModel用了一个枚举 MTLPropertyStorage 来标记一个属性的拷贝行为,分为三类:

  1. MTLPropertyStorageNone :属性不做任何存储,在 MTLModel里判断不存储的条件是1.没有该属性,自然不用存储 2.该属性没有使用 @dynamic 指令,但是没有成员变量,并且没有对应的setter和getter方法 3. MTLModel 类中属性是只读,且没有成员变量。

  2. MTLPropertyStorageTransitory :属性只做暂时性的存储,在官方解释里看到一句话 It may disappear at any time ,感觉指的是弱引用的属性,但是在 MTLModel 里并没有看到返回 MTLPropertyStorageTransitory 的处理。

  3. MTLPropertyStoragePermanent ,属性做永久存储, MTLModel 里判断只要不是 MTLPropertyStorageNone 就是 MTLPropertyStoragePermanent,需要做暂时存储的,就需要在子类里重写了。

遍历所有属性方法

第4步循环遍历所有属性(包括本类及其父类)的方法为:

循环遍历属性方法

这里遍历类及其父类,一直到 MTLModel 根类为止。获取它的属性,如果没有属性值继续父类,如果有,使用 onExit 语法(相当于 Swift 上 defer 关键字),在作用域结束的时候,释放掉 properties。

onExit 定义:

onExit 定义

attribute((cleanup(mtl_executeCleanupBlock)) 在作用域离开的时候,进行清理工作。会调用mtl_executeCleanupBlock函数,并将 block 的地址传入,在函数体内直接调用这个 block。

验证属性的存储类型方法:

验证属性的存储类型方法

这里面用到一个属性结构,方便管理一个属性的描述内容。如果一个属性没有getter, setter, 并且没有对应的变量,则返回.none 类型。如果是只读且没有对应的变量,到了根类 MTLModel 返回 none,否则验证父类的关于这个键值的存储类型。其他返回的都是 permanent 类型。

到目前为止,MTLModel 将 model转 dictionary 就完成了。

接下来分析一下 MTLJSONAdapter 类和 MTLJSONSerializing 协议。

MTLJSONSerializing

MTLJSONSerializing协议方法

第一个方法,是必须实现的方法,它是用于将属性和 JSON 的解析路径做关联,它不仅仅可以用于给属性起“别名”,还可以用于多级解析和多层嵌套,用官方例子来举例:

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
   return @{
       @"name": @"POI.name",
       @"point": @[ @"latitude", @"longitude" ],
       @"starred": @"starred"
   };
}
在映射过程中 starred 与 JSONDictionary[@"starred"] 做映射,
name 与 JSONDictionary[@"POI"][@"name"] 做映射,
point 则等同于以下这个 dictionary
@{
   @"latitude": JSONDictionary[@"latitude"],
   @"longitude": JSONDictionary[@"longitude"]
 }

第二个方法,是 model 实现的 +< key >JSONTransformer 方法,如果有则使用它进行转换的值。

第三个方法是用于类簇。

@interface XYMessage : MTLModel

@end

@interface XYTextMessage: XYMessage

@property (readonly, nonatomic, copy) NSString *body;

@end

@interface XYPictureMessage : XYMessage

@property (readonly, nonatomic, strong) NSURL *imageURL;

@end

@implementation XYMessage

+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary {
    if (JSONDictionary[@"image_url"] != nil) {
        return XYPictureMessage.class;
    }

    if (JSONDictionary[@"body"] != nil) {
        return XYTextMessage.class;
    }

    NSAssert(NO, @"No matching class for the JSON dictionary '%@'.", JSONDictionary);
    return self;
}

@end

MTLJSONAdapter

1. JSON data -> using Model class -> model object

第一个方法的核心内容是 modelFromJSONDictionary: error: 方法。数组的类型,需要循环遍历数组内的 JSONDictionary 数据,调用第一个方法。

  1. 从 model class 上获取所有的属性,参考上面的MTLModel propertyKeys方法;

  2. 遍历这些属性,并且根据属性作为 key 获取 JSON 上的 value;如果没有值,不用转换,直接 continue,继续下一个循环;

  3. 获取属性的转换器 valueTransformer,用户从 JSON value 转换到自定义的值,比如 long 类型的时间戳,转换到 NSDate 类型,也可以从 JSON的字符串,映射到 Objective-C 枚举类型;

  4. 将 key 和转换后的 value,构建一个键值对,保存起来,最终形成一个 dicionaryValue;

  5. 使用 MTLModel 的Dictionary 转换成 Model 对象的方法;

  6. 使用 KVC 验证转换后的 model 是否有效,无效返回 nil,有效则返回 model 对象。

通过以上六步,将 JSON data,通过 Model class,转换成 model object。

2. model object -> using Model class -> JSON data

第一个方法的核心内容是 JSONDictionaryFromModel: error: 方法,数组类型的,需要循环遍历数组中 model 对象,将其出入第一个方法。

  1. 核对 model class 类型;

  2. 获取需要转换的 keys。 model 实现的 MTLJSONSerializing 协议中的 JSONKeyPathsByPropertyKey 方法,并将这些 keys 对应 model class 的 values,构建 dictionaryValue;

  3. 遍历dictionaryValue, 获取 JSON 上的 keyPaths;

  4. 获取 model 定义的 value transformer;

  5. 是否允许反向转换;

  6. 如果定义这个 reverseTransformedValue: success: error: 方法,转换错误处理,更加安全;

  7. 如果没有,调用 reverseTransformedValue 换成 value。如果返回 nil,则设置为 NSNull.null;

  8. 创建一个根据 keyPath,分割键值,填充对象的 createComponents block;

  9. 如果 JSONKeyPaths是 字符串类型,则将转换后的 value 对象,key 为 JSONKeyPaths, 构建键值对,并保存起来;

  10. 如果 JSONKeyPaths 是数组,遍历这个路径, 对于每个 value, 调用第八步的 createComponents block,并且执行第九步的操作。

经过以上十步,完成了model object 通过 model class,转换成 JSON dictionary data,可以将其发送给服务器端。

以上,就是 Mantle 实现的核心内容。

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

推荐阅读更多精彩内容