Mantle

Mantle是什么?

GitHub上的介绍是:

Model framework for Cocoa and Cocoa Touch

这是一个模型框架。那么具体有什么作用?

回忆一下,在开发过程中有没有经常和后台人员沟通关于模型字段命名的问题,是后台人员遵守你的规则,还是你遵守他的规则,或者说各自用不同的。因为这涉及到序列化和反序列化的问题。当然,如果在字段统一的情况下,只需要一句代码就能完成从字典向模型的转换- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;,但是在实际开发过程中,几乎很难做到这点。比如 id 在Objective-C中是保留字段。Mantle提供了一个转换方法:根据自定义的属性映射关系进行序列化和反序列化,简单的说就是字段转换。

如何使用?

字段转换

Mantle提供了一个基本类:MTLModel,如果你想使用Mantle的各种功能,那么你所创建的模型必须是这个类的子类。
举个例子,创建一个Member类

Member.h

@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> 协议,查看这个协议你会发现里面有个必须实现的方法:

+ (NSDictionary *)JSONKeyPathsByPropertyKey;

这个方法就是前面提到的用于字段转换的,下面实现这个方法

Member.m

+ (NSDictionary *)JSONKeyPathsByPropertyKey{
    return @{
             @"memberID" : @"id",
             @"mobilePhone" : @"phone",
             @"createDate" : @"date",
             @"goldNumber" : @"goldNumber"
             };
}

这里的意思是:客户端这边的memberID字段对应服务端返回的数据中id字段。注意:本地字段在前,服务端字段在后。完成这个方法就代表着当进行序列化或者反序列化的时候,就会根据这个属性映射关系来进行。 当然,如果key值相同的话就不需要写对应关系了

注意:最新的2.0版本中,不能再省略相同的字段。也就是说 + (NSDictionary *)JSONKeyPathsByPropertyKey 方法中返回的字典,@"goldNumber" : @"goldNumber" 必须写。如果不写,就相当于不进行序列化,此字段对应的值将位空。

最后用一句代码来得到你想要的模型

    NSDictionary *response = @{
                          @"id" : @"1",
                          @"phone" : @"xxxxxxxx",
                          @"date" : @"2014-09-09",
                          @"goldNumber" : @2
                          };
    Member *member = [MTLJSONAdapter modelOfClass:[Member class] fromJSONDictionary:response error:nil];

是的,这样就完成了字段转换,比起写繁杂的if/else来做字段转换,简直方便多了。

当然,Mantle的功能不止上述的一个。介绍其他功能之前,我们先为Member增加几个字段

@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>中的方法:

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

具体实现

+ (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:

+ (instancetype)transformerUsingForwardBlock:(MTLValueTransformerBlock)forwardTransformation reverseBlock:(MTLValueTransformerBlock)reverseTransformation;

第一个Block的返回的值是字典–>模型转换的结果,第二Block的返回值是模型–>字典转换的结果。当然如果我们只需要序列化,那么就实现单向转换即可,使用下列API:

+ (instancetype)transformerUsingForwardBlock:(MTLValueTransformerBlock)transformation;

Mantle其实还提供了另一种实现方式,同样的实现上述功能

+ (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类型的有更快捷的方法:

+ (NSValueTransformer *)urlJSONTransformer{
    return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
}
+ (NSValueTransformer *)isVipJSONTransformer{
    return [NSValueTransformer valueTransformerForName:MTLBooleanValueTransformerName];
}

最后关于age字段的转换

+ (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类型也可以

空对象处理

先来看一段代码

    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

- (void)setNilValueForKey:(NSString *)key{
    if ([key isEqualToString:@"isVip"]) {
        self.isVip = 0;
    }
    else{
        [super setNilValueForKey:key];
    }
}

这种问题其实只针对于非指针类型,像float,bool,double。

Core Data相关

Mantle还提供了一个专门操作Core Data的类 MTLManagedObjectAdapter ,其中包括有些非常有用的方法,比如:唯一性检查、实体属性转换等。下一篇博文我将着重讲述 MagicalRecord 配合Mantle的使用。

最后

Mantle当然还有其他一些功能
* 归档:已实现了NSCoding协议
* 比较:- (BOOL)isEqual:(id)object;,默认实现-hash

Demo地址:MagicalRecord-Mantle

    原文作者:bawn
    原文地址: https://segmentfault.com/a/1190000002431354
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞