对 JSON 解析库目前流行的也是非常的多,jsonmodelMantleYYModel 等都是大家常用的,使用这些库的好处就是你无需关心数据解析转换和复杂的类型判断处理,你只需要确定好客户端的数据结构能够与后端数据结构相对应即可,而这些库里面对于解析的处理也都是大同小异,无非是在使用、性能和异常处理上有些差别,这里推荐一下郭曜源大神( YYModel 的作者)的文章,内容是对各大解析库进行了比较,对于开发者来说,了解其解析过程是最为基础的,这次看完 Mantle 的解析处理,我也做个简单的总结,大家可以互相学习交流。

首先 Mantle 对于 JSON 解析的过程分为三步:

第一步是对 Model 字段的转换处理

第二步是对 JSON Dictionary 值的转换处理

第三步是对 Model 的赋值

0x01

对于解析 JSON 数据,我们比较常用是以下这个方法,modelClass 指的是继承于 MTLModel 的子类,也是我们需要解析成的目标 Model 类, JSONDictionary 则是需要解析的 JSON 数据。

1
2
3
4
5
+ (id)modelOfClass:(Class)modelClass fromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error {
MTLJSONAdapter *adapter = [[self alloc] initWithModelClass:modelClass];

return [adapter modelFromJSONDictionary:JSONDictionary error:error];
}

实现上看就两步,先根据 modelClass 初始化一个适配器,再根据 JSON 数据返回 model 对象。

初始化适配器的内容比较多,我们分段来说几个关键的地方。

首先检查的就是 Model 是否实现了 MTLJSONSerializing 协议,未实现的话会触发断言。

1
2
3
4
5
6
7
8
9
10
11
12
13
- (id)initWithModelClass:(Class)modelClass {
NSParameterAssert(modelClass != nil);
NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);

self = [super init];
if (self == nil) return nil;

_modelClass = modelClass;

_JSONKeyPathsByPropertyKey = [modelClass JSONKeyPathsByPropertyKey];

...
}

那么先来看下 MTLJSONSerializing 协议,

1
2
3
4
5
6
7
8
9
@protocol MTLJSONSerializing <MTLModel>
@required
+ (NSDictionary *)JSONKeyPathsByPropertyKey;

@optional
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key;
+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary;

@end

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+ (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"]
}

这样的设计很是方便,可以自由定义映射解析之间的关系,但是在实现的时候会发现,我们在处理对于像 starred 这样普通的解析关系时,仍旧需要一个一个重新在定义一遍,如果我们的数据结构内容非常的多,那就是个体力活了,而且不定义就无法解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (id)initWithModelClass:(Class)modelClass {
...

NSSet *propertyKeys = [self.modelClass propertyKeys];

...
}

+ (NSSet *)propertyKeys {
NSSet *cachedKeys = objc_getAssociatedObject(self, MTLModelCachedPropertyKeysKey);
if (cachedKeys != nil) return cachedKeys;

NSMutableSet *keys = [NSMutableSet set];

[self enumeratePropertiesUsingBlock:^(objc_property_t property, BOOL *stop) {
NSString *key = @(property_getName(property));

if ([self storageBehaviorForPropertyWithKey:key] != MTLPropertyStorageNone) {
[keys addObject:key];
}
}];

objc_setAssociatedObject(self, MTLModelCachedPropertyKeysKey, keys, OBJC_ASSOCIATION_COPY);

return keys;
}

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

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

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

第二类 MTLPropertyStorageTransitory :属性只做暂时性的存储,在官方解释里看到一句话 It may disappear at any time ,感觉指的是弱引用的属性,但是在 MTLModel 里并没有看到返回 MTLPropertyStorageTransitory 的处理,但在 MTLTestModel 中可以找到, MTLTestModel 对 storageBehaviorForPropertyWithKey: 进行了重写,其中它对属性名进行了判断,在对应的头文件里我们也可以找到这个属性的说明:Should not be stored in JSON, has MTLPropertyStorageTransitory.

1
2
3
4
5
6
7
+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey {
if ([propertyKey isEqual:@"weakModel"]) {
return MTLPropertyStorageTransitory;
} else {
return [super storageBehaviorForPropertyWithKey:propertyKey];
}
}

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

0x02

1
2
3
4
5
6
7
8
- (id)initWithModelClass:(Class)modelClass {
...

_valueTransformersByPropertyKey = [self.class valueTransformersForModelClass:modelClass];
_JSONAdaptersByModelClass = [NSMapTable strongToStrongObjectsMapTable];

return self;
}

在遍历完之后开始对属性的值进行转换操作,对于转换操作也提供了三种方式,

第一种是自定义方式,在子类里定义实现方法名为 key(属性名) + JSONTransformer 的方法,例如:

1
2
3
+ (NSValueTransformer *)URLJSONTransformer {
return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
}

第二种是重写 MTLJSONSerializing 的 optional 协议,

1
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key;

例如:

1
2
3
4
5
6
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key {
return @{
// Not provided transformer for self.URL
@"otherURL": [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName],
}[key];
}

第三种则是根据属性的类型做相应的转换。

在以上两部转换完成之后剩下的就是赋值了,因为实现了 JSONKeyPathsByPropertyKey 方法,所以从 JSONDictionary 取值时如果碰到以数组形式定义的 key 时,会以.进行拆分,例如 @"name": @"POI.name" ,则对应的取值处理是 JSONDictionary[@"POI"][@"name”]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- (id)modelFromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error {
...

for (NSString *propertyKey in [self.modelClass propertyKeys]) {
id JSONKeyPaths = self.JSONKeyPathsByPropertyKey[propertyKey];

if (JSONKeyPaths == nil) continue;

id value;

if ([JSONKeyPaths isKindOfClass:NSArray.class]) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];

for (NSString *keyPath in JSONKeyPaths) {
BOOL success = NO;
id value = [JSONDictionary mtl_valueForJSONKeyPath:keyPath success:&success error:error];

if (!success) return nil;

if (value != nil) dictionary[keyPath] = value;
}

value = dictionary;
} else {
BOOL success = NO;
value = [JSONDictionary mtl_valueForJSONKeyPath:JSONKeyPaths success:&success error:error];

if (!success) return nil;
}

...
}

...
}

还记得我们在子类里定义的 keyTransform 方法么,在取值之后这些值都会做相应的转换,注意一点就是NSNull对象会先转换成nil,然后将nil转换成NSNull对象插入到新的 dictionaryValue 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- (id)modelFromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error {
...

for (NSString *propertyKey in [self.modelClass propertyKeys]) {
...

@try {
NSValueTransformer *transformer = self.valueTransformersByPropertyKey[propertyKey];
if (transformer != nil) {
// Map NSNull -> nil for the transformer, and then back for the
// dictionary we're going to insert into.
if ([value isEqual:NSNull.null]) value = nil;

if ([transformer respondsToSelector:@selector(transformedValue:success:error:)]) {
id<MTLTransformerErrorHandling> errorHandlingTransformer = (id)transformer;

BOOL success = YES;
value = [errorHandlingTransformer transformedValue:value success:&success error:error];

if (!success) return nil;
} else {
value = [transformer transformedValue:value];
}

if (value == nil) value = NSNull.null;
}

dictionaryValue[propertyKey] = value;
} @catch (NSException *ex) {
NSLog(@"*** Caught exception %@ parsing JSON key path \"%@\" from: %@", ex, JSONKeyPaths, JSONDictionary);

...
}
}

...
}

0x03

在生成 dictionaryValue 之后,开始调用 - (instancetype)initWithDictionary:(NSDictionary *)dictionary error:(NSError **)error 来对子类进行初始化,并调用 MTLValidateAndSetValue 来赋值,至此从 JSONDictionary 到 model 的解析算是完成了~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (instancetype)initWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {
self = [self init];
if (self == nil) return nil;

for (NSString *key in dictionary) {
// Mark this as being autoreleased, because validateValue may return
// a new object to be stored in this variable (and we don't want ARC to
// double-free or leak the old or new values).
__autoreleasing id value = [dictionary objectForKey:key];

if ([value isEqual:NSNull.null]) value = nil;

BOOL success = MTLValidateAndSetValue(self, key, value, YES, error);
if (!success) return nil;
}

return self;
}