首页 Mantle
文章
取消

Mantle

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

本文由作者按照 CC BY 4.0 进行授权

杂记

MagicalRecord