Mantle的基本使用
Mantle是什么?
GitHub上的介绍是:
Model framework for Cocoa and Cocoa Touch
这是一个模型框架。那么具体有什么作用?
回忆一下,在开发过程中有没有经常和后台人员沟通关于模型字段命名的问题,是后台人员遵守你的规则,还是你遵守他的规则,或者说各自用不同的。因为这涉及到序列化和反序列化的问题。当然,如果在字段统一的情况下,只需要一句代码就能完成从字典向模型的转换- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
,但是在实际开发过程中,几乎很难做到这点。比如 id
在Objective-C中是保留字段。Mantle提供了一个转换方法:根据自定义的属性映射关系进行序列化和反序列化,简单的说就是字段转换。
如何使用?
字段转换
Mantle提供了一个基本类:MTLModel
,如果你想使用Mantle的各种功能,那么你所创建的模型必须是这个类的子类。 举个例子,创建一个Member类
Member.h
1
2
3
4
5
@interface Member : MTLModel<MTLJSONSerializing>
@property (nonatomic, retain) NSString *memberID;
@property (nonatomic, retain) NSString *mobilePhone;
@property (nonatomic, retain) NSDate *createDate;
@property (nonatomic, retain) NSNumber *goldNumber;
Member的父类是 MTLModel
同时还遵守 <MTLJSONSerializing>
协议,查看这个协议你会发现里面有个必须实现的方法:
1
+ (NSDictionary *)JSONKeyPathsByPropertyKey;
这个方法就是前面提到的用于字段转换的,下面实现这个方法
Member.m
1
2
3
4
5
6
7
8
+ (NSDictionary *)JSONKeyPathsByPropertyKey{
return @{
@"memberID" : @"id",
@"mobilePhone" : @"phone",
@"createDate" : @"date",
@"goldNumber" : @"goldNumber"
};
}
这里的意思是:客户端这边的memberID
字段对应服务端返回的数据中id
字段。注意:本地字段在前,服务端字段在后。完成这个方法就代表着当进行序列化或者反序列化的时候,就会根据这个属性映射关系来进行。 当然,如果key值相同的话就不需要写对应关系了
注意:最新的2.0版本中,不能再省略相同的字段。也就是说 + (NSDictionary *)JSONKeyPathsByPropertyKey
方法中返回的字典,@"goldNumber" : @"goldNumber"
必须写。如果不写,就相当于不进行序列化,此字段对应的值将为空。
最后用一句代码来得到你想要的模型
1
2
3
4
5
6
7
8
}
NSDictionary *response = @{
@"id" : @"1",
@"phone" : @"xxxxxxxx",
@"date" : @"2014-09-09",
@"goldNumber" : @2
};
Member *member = [MTLJSONAdapter modelOfClass:[Member class] fromJSONDictionary:response error:nil];
是的,这样就完成了字段转换,比起写繁杂的if/else来做字段转换,简直方便多了。
注意:如果 model 的字段和 json 数据是完全对应的,使用
1
2
3
+ (NSDictionary *)JSONKeyPathsByPropertyKey{
return [NSDictionary mtl_identityPropertyMapWithModel:self];
}
即可
当然,Mantle的功能不止上述的一个。介绍其他功能之前,我们先为Member增加几个字段
1
2
3
4
5
6
7
8
@interface Member : MTLModel<MTLJSONSerializing>
@property (nonatomic, retain) NSString *memberID;
@property (nonatomic, retain) NSString *mobilePhone;
@property (nonatomic, retain) NSDate *createDate;
@property (nonatomic, retain) NSNumber *goldNumber;
@property (nonatomic, assign) NSUInteger age;
@property (nonatomic, assign) BOOL isVip;
@property (nonatomic, retain) NSURL *url;
拿createDate字段来说,如果想在模型中直接得到NSDate类,就必须进行NSString–>NSDate的类型转换。
类型转换
和字段转换的实现方式一样,必须实现<MTLJSONSerializing>
中的方法:
1
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key;
具体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key{
if ([key isEqualToString:@"createDate"]) {
return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *string, BOOL *success, NSError *__autoreleasing *error) {
return [self.dateFormatter dateFromString:string];
} reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) {
return [self.dateFormatter stringFromDate:date];
}];
}
else{
return nil;
}
}
+ (NSDateFormatter *)dateFormatter {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy-MM-dd";
return dateFormatter;
}
API:
1
+ (instancetype)transformerUsingForwardBlock:(MTLValueTransformerBlock)forwardTransformation reverseBlock:(MTLValueTransformerBlock)reverseTransformation;
第一个Block的返回的值是字典
–>模型
转换的结果,第二Block的返回值是模型
–>字典
转换的结果。当然如果我们只需要序列化,那么就实现单向转换即可,使用下列API:
1
+ (instancetype)transformerUsingForwardBlock:(MTLValueTransformerBlock)transformation;
Mantle其实还提供了另一种实现方式,同样的实现上述功能
1
2
3
4
5
6
7
+ (NSValueTransformer *)createDateJSONTransformer{
return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *string, BOOL *success, NSError *__autoreleasing *error) {
return [self.dateFormatter dateFromString:string];
} reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) {
return [self.dateFormatter stringFromDate:date];
}];
}
方法命名规则是:+<key>JSONTransformer
,另外对于BOOL和NSURL类型的有更快捷的方法:
1
2
3
4
5
6
+ (NSValueTransformer *)urlJSONTransformer{
return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
}
+ (NSValueTransformer *)isVipJSONTransformer{
return [NSValueTransformer valueTransformerForName:MTLBooleanValueTransformerName];
}
最后关于age字段的转换
1
2
3
4
5
6
7
+ (NSValueTransformer *)ageJSONTransformer{
return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *string, BOOL *success, NSError *__autoreleasing *error) {
return @([string integerValue]);
} reverseBlock:^id(NSNumber *number, BOOL *success, NSError *__autoreleasing *error) {
return [number stringValue];
}];
}
为什么返回 NSNumber 到 Model 内部就成了 NSUInteger 这个要得益于 KVC, KVC可以自动的将数值或结构体型的数据打包或解包成 NSNumber或 NSValue对象
空对象处理
先来看一段代码
1
2
3
4
5
6
7
8
9
NSDictionary *response = @{@"id" : @"1",
@"phone" : @"xxxxxx",
@"date" : @"2014-09-09",
@"goldNumber" : @2,
@"age" : @"18",
@"url" : @"http://bawn.github.io/",
@"isVip" : NSNull.null
};
Member *member = [MTLJSONAdapter modelOfClass:[Member class] fromJSONDictionary:response error:nil];
这里模拟的是服务端返回空的isVip字段对应的值,运行的结果当然是crash,Mantle也为这种情况提供了解决办法,实现Mantle内部会把值转换为nil,然后需要我们去实现 - (void)setNilValueForKey:(NSString *)key;
方法即可
Member.m
1
2
3
4
5
6
7
8
- (void)setNilValueForKey:(NSString *)key{
if ([key isEqualToString:@"isVip"]) {
self.isVip = 0;
}
else{
[super setNilValueForKey:key];
}
}
这种问题其实只针对于非指针类型,像float,bool,double。
坑
一般情况下对于字段值是链接时,基本都会这样处理
1
2
+ (NSValueTransformer *)linkJSONTransformer{
return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
但如果服务端返回过来的 urlString 是这样的:http://www.luisaviaroma.com/index.aspx?#ItemSrv.ashx|SeasonId=63I
, 注意:字符串里面有个|
字符,存在这样的转义字符Mantle
在映射的时候就会出错,导致整个Model返回为空,这时候就很难排查
所以如果整个Model莫名其妙返回为空请检查是否有这种情况,或者不使用这个方法,改用:
1
2
3
4
5
+ (NSValueTransformer *)linkJSONTransformer{
return [MTLValueTransformer transformerUsingForwardBlock:^id(id value, BOOL *success, NSError *__autoreleasing *error) {
return [NSURL URLWithString:value];
}];
}
Core Data相关
Mantle还提供了一个专门操作Core Data的类 MTLManagedObjectAdapter
,其中包括有些非常有用的方法,比如:唯一性检查、实体属性转换等。下一篇博文我将着重讲述 MagicalRecord 配合Mantle的使用。
最后
Mantle当然还有其他一些功能
- 归档:已实现了NSCoding协议
- 比较:
- (BOOL)isEqual:(id)object;
,默认实现-hash
Demo地址:MagicalRecord-Mantle