浅谈Mantle
使用Mantle
也差不多一年,使用期间总算积累了一些经验,在此不吝献丑,或能有益于人;由于是一些知识点的汇总,难免芜杂,见谅!所有索引都随文给出,不另外附录;建议核心部分,结合代码理解。
解析过程
Mantle
的核心类是MTLJSONAdapter
,负责整个解析。下面以JSON To Object
为例粗略介绍一下。相关代码见此
|
这个函数,前面部分是处理类似类簇的一类model,并不是很常见的需求,就不展开了;接下来就是整个常规的解析流程, 无非是遍历整个propertyKeys
, 依次解析。
下面跟踪一遍单个property
的解析。
单个
property
的解析前面一小半是处理花式keypath
的写法,无关大体。真正的解析其实就是这20行代码。 处理keypath
拿到单个property
对应的JSON Value
, 然后通过valueTransformersByPropertyKey
取出对应transformer,转化Value
为相应的格式。单个property
的解析即完成。
解析完所有propertyKeys
,就会通过转化后的dic集由modelWithDictionary:
去初始化ModelClass
,这个过程会走一下validate
流程最后通过KVC
去设置property
。这就是整个解析过程,当然这里忽略掉了类型不匹配和validate不通过导致解析失败,返回error的过程。
三类Transformer
综上可以看出整个解析流程其实非常清晰明了的,问题核心在transformer
一个JSON Value
到Custom Object
。下面看一下transformer
的相关代码。
|
按性质我把transformer
分为三类:
keyTransformer
key即为property name
。
首先根据key动态构造的
keyJSONTransformer
去获取自定义的transformer
;未果则转入JSONTransformerForKey:(NSSString *)key
获取;未果fallback到typeTransformer
。这类transformer
主要用于对一个指定的property
构造一个特定的转换,可以满足任意类型的定制。
typeTransformer
type
即为property
的类型,运行时获取,对象类型和基础类型处理上会有一定差别。
对象类型
先根据
transformerForModelPropertiesOfClass:
去获取针对一些预置的,未实现MTLJSONSerializing
类型的transformer
,比如NSURL,NSDate(目前Mantle里面只实现了NSURL,至于为什么没有实现别的类型见这个issue);未果,当前Class
若实现MTLJSONSerializing
, 则为它生成一个transformer,这个是我们应用中的大部分场景,即一大堆自定义的Model
,我们不需要做任何其他的事,只需要声明property
时写上对应的类型就可以了,是不是人心大快!仍旧未果,就会通过mtl_validatingTransformerForClass:
去生成一个validate
的transformer(这类transformer的作用后面详述)。
基础类型
比较简单。首先通过
transformerForModelPropertiesOfObjCType:
获取一个transformer
;未果也是生成一个validate
的transformer
。
KVC也算一类transformer
最终
setValue
时会调用KVC
去完成,KVC
对基础类型有个自动解包,也可以算作是一类transformer
。
Transformer的使用
基本使用
结合上面的解析和transformer
的生成流程,可以看出我们的生活确实简单了。首先在property
里面声明好属性,然后通过JSONKeyPathsByPropertyKey
做好property
和json key
的映射关系,基本上整个Model
基本就完成了。
Array的处理
但这个过程有个例外,就是对NSArray
的处理。目前OC里面的Array
泛型是编译时的,运行时获取不到数组中对象类型,这就需要我们通过通过arrayTransformerWithModelClass:
构造一个上面的typeTransformer
返回。但直到某一天看到有人给YYModel
提了个issue,发现这个问题似乎已经有了一个可能的解法。构造一个与Model Class
同名的protocol
让NSArray
conform,这时通过runtime
就可以取到这个protocol name
,作为Model Class
的name构造一个type transformer
。这个feature JSONModel应该支持了一段时间,目前YY已经集成,Mantle
何时支持,拭目以待!
KVC里的小烦恼
解析完成后会调用KVC去setValue。但KVC有个特性需要注意下,- (void)setValue:(nullable id)value forKey:(NSString *)key
当key是基础类型而value是NSNull时会调用- (void)setNilValueForKey:(NSString *)key
, 默认这个会崩溃,但Mantle并没有给你处理这种情况…所以需要我们在给基类给一个默认实现。
typeTransformer有什么用
从上面我们可以看到系统已经为NSURL添加了默认的transformer,即NSURLJSONTransformer
。对于UIColor,NSDate这类对象,如果整个应用的格式是统一的,按理我们也可以加上对这类对象的默认transformer
,使用方只要声明好自己好property
的类型就可以了。
巧用typeTransformer
结合type transformer
其实我们还可以做一些更巧妙的事情。有一些类似ID的主键,服务器因为存储的是整形,出于方便和效率方面的考虑,服务端会直接返回一个整形的数据。但客户端在使用类型一直会把这个类型当做字符串使用,很多情况下就不免加上stringValue来转换类型或者加上显示的keyTransformer去显示转化。这时其实可以考虑加上一个NSStringJSONTransformer,转化可能的NSNumber为string,可谓一劳永逸。但这个问题方法也有个弊端就是丢掉了property
原始的类型,如果需要再次把model序列化成对应的JSON就尴尬了,不过目前在应用中还算使用较少,基本就忽略。
Validate
validate从性质上也可以分为两类,一类为校验JSON数据是否返回了约定的格式,另一类校验返回的数据是否业务场景。
数据格式的校验(Mantle世界的麦田守望者)
校验数据格式的工作基本有上文提到的validate transformer
提供,如果是提供了自定义的transformer
, 则需要自己提供对原始格式的严格检验。这个transformer
位置重要,堪比麦田守望者。它会校验此时的json object
是否是符合声明的类型,不是则及时报错。如果没有这个transformer
,试想我们预期是NSString
而拿到一个NSNumber
会发生什么!数据格式错误往往是服务器出现了什么重大错误,或者http请求被恶意劫持,比较难以根治和解决,有了这个检验至少可以显著降低客户端的崩溃率。
业务逻辑的校验
业务逻辑的校验需要我们自己实现,返回的数据此时虽已确保类型无误。但我们还是不能确保它就是符合业务规范的,比如说关键的主键却为空,或者关联属性出现互相矛盾的结果。这时我们就需要我们根据业务需求去实现validate。理论上当然是逻辑越严密越好,但这时会导致代码异常繁琐,现实中一般做的并不多。因为这类错误,基本是服务端程序员的逻辑错误,所以只要他们能即使改正,客户端是可保无虞。
巧妙解决非空的判断
结合上面的NSArray的处理,其实我们也可以实现一个简单的Not NULL的实现,声明一个NotNUll
的protocol,非空NSString
,NSNumber
属性conform一下。然后通过runtime
去validate相关属性,是不是感觉清晰了不少…
一些实践
写好keypath
keypath推荐结合用extobjc结合code snippets,编译时检查,值得拥有。
加入默认值
有些时候,默认值是非空或0,可以在MTModel的基类里面的- (instancetype)initWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {
里面加入一些默认值的处理。
好吧,不写了。