用Mantle构建Model层

作者: ch32053 | 来源:发表于2014-11-04 15:55 被阅读8735次

在项目开发过程中,经常要自定义Model,然后在请求服务器得到数据后(一般是Json数据),用字典取值的方式给自定义的Model赋值,封装成数据对象。这样做有几个问题:

  • 服务器更新字段(或者添加字段)后,客户端要在每个Model初始化的地方修改(或者添加)取值字段,过程繁琐。
  • 实现这些自定义的Model对象序列化保存到本地,需要自己一个一个实现,在字段添加或者修改的时候也要一个一个更改,过程繁琐。
  • 自定义的Model没办法Copy,除非你实现<copying>协议,没办法反序列化成Json。

庆幸的是,伟大的github工程师们在OC平台上提供了一个设计优化、高度统一的框架Mantle来解决这些问题。

Mantle为我们带来的:

  • 实现了NSCopying protocol,子类终于可以直接copy了
  • 实现了NSCoding protocol,可以通过NSKeyedArchiver保存到本地了。(NSUserDefaults 的替换选择)
  • 提供了-isEqual:和-hash的默认实现,model作NSDictionary的key方便了许多
  • 能在Model 和 Json 元数据之间相互转换

Model的基本用法

自定义的Model

自定义的Model都需要集成自MTLModel,并且实现MTLJSONSerializing协议,例如下面的:

typedef enum : NSUInteger {
    GHIssueStateOpen,
    GHIssueStateClosed
} GHIssueState;

@interface GHIssue : MTLModel <MTLJSONSerializing>

@property (nonatomic, copy, readonly) NSURL *URL;
@property (nonatomic, copy, readonly) NSURL *HTMLURL;
@property (nonatomic, copy, readonly) NSNumber *number;
@property (nonatomic, assign, readonly) GHIssueState state;
@property (nonatomic, copy, readonly) NSString *reporterLogin;
@property (nonatomic, strong, readonly) GHUser *assignee;
@property (nonatomic, copy, readonly) NSDate *updatedAt;

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *body;

@property (nonatomic, copy, readonly) NSDate *retrievedAt;

@end

m 文件如下

@implementation GHIssue

+ (NSDateFormatter *)dateFormatter {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
    dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'Z'";
    return dateFormatter;
}

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"URL": @"url",
        @"HTMLURL": @"html_url",
        @"reporterLogin": @"user.login",
        @"assignee": @"assignee",
        @"updatedAt": @"updated_at"
};
}

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

+ (NSValueTransformer *)HTMLURLJSONTransformer {
    return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
}

+ (NSValueTransformer *)stateJSONTransformer {
    return [NSValueTransformer mtl_valueMappingTransformerWithDictionary:@{
        @"open": @(GHIssueStateOpen),
        @"closed": @(GHIssueStateClosed)
    }];
}

+ (NSValueTransformer *)assigneeJSONTransformer {
    return [NSValueTransformer mtl_JSONDictionaryTransformerWithModelClass:GHUser.class];
}

+ (NSValueTransformer *)updatedAtJSONTransformer {
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) {
        return [self.dateFormatter dateFromString:str];
    } reverseBlock:^(NSDate *date) {
        return [self.dateFormatter stringFromDate:date];
    }];
}

- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error {
    self = [super initWithDictionary:dictionaryValue error:error];
    if (self == nil) return nil;

    // Store a value that needs to be determined locally upon initialization.
    _retrievedAt = [NSDate date];

    return self;
}

@end

MTLJSONSerializing

继承自MTLModel并实现了MTLJSONSerializing协议的对象可以这样转换

从字典数据(JSONDictionary表示字典元数据)到 Model:

NSError *error = nil;
XYUser *user = [MTLJSONAdapter modelOfClass:XYUser.class    fromJSONDictionary:JSONDictionary error:&error]; 

从Model到JSONDictionary数据

NSDictionary *JSONDictionary = [MTLJSONAdapter JSONDictionaryFromModel:user];
+ (NSDictionary *)JSONKeyPathsByPropertyKey;用法如下:
@interface XYUser : MTLModel

@property (readonly, nonatomic, copy) NSString *name;
@property (readonly, nonatomic, strong) NSDate *createdAt;

@property (readonly, nonatomic, assign, getter = isMeUser) BOOL meUser;
@property (readonly, nonatomic, strong) XYHelper *helper;

@end

@implementation XYUser

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"createdAt": @"created_at",
        @"meUser": NSNull.null
    };
}

- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error {
    self = [super initWithDictionary:dictionaryValue error:error];
    if (self == nil) return nil;

    _helper = [XYHelper helperWithName:self.name createdAt:self.createdAt];

    return self;
}

@end

返回的字典用来指定该Model的属性从字典里面怎么取值,比如createdAt是从字典里面取created_at字段,指定@"meUser": NSNull.null表示不从字典里面取值,没有在上面列出的Model属性取和属性同名的字典字段(比如name就从字典里面取name字段)。

+JSONTransformerForKey: 用法
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key {
    if ([key isEqualToString:@"createdAt"]) {
        return [NSValueTransformer valueTransformerForName:XYDateValueTransformerName];
    }

    return nil;
}

实现上面的方法,用来指定属性从字典数据里面取出来的是什么类型的数据。比如上面createdAt属性从字典里面取值后会自动转换为Date类型。

如果有很多类型需要指定取值类型,那岂不有很多if,这太不优雅了!Mantle提供了更优雅的方法:实现类似+<key>JSONTransformer的方法,来指定某个属性从字典里面取值后的类型(或怎么取值):

+ (NSValueTransformer *)createdAtJSONTransformer {
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) {
        return [self.dateFormatter dateFromString:str];
    } reverseBlock:^(NSDate *date) {
        return [self.dateFormatter stringFromDate:date];
    }];
}
+classForParsingJSONDictionary: 用法

当你自定义了一组Model,实现+classForParsingJSONDictionary:方法可以指定在转换deserializing字典的时候,用那个Model class

@interface XYMessage : MTLModel

@end

@interface XYTextMessage: XYMessage

@property (readonly, nonatomic, copy) NSString *body;

@end

@interface XYPictureMessage : XYMessage

@property (readonly, nonatomic, strong) NSURL *imageURL;

@end

@implementation XYMessage

+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary {
    if (JSONDictionary[@"image_url"] != nil) {
        return XYPictureMessage.class;
    }

    if (JSONDictionary[@"body"] != nil) {
        return XYTextMessage.class;
    }

    NSAssert(NO, @"No matching class for the JSON dictionary '%@'.",       JSONDictionary);
    return self;
}

@end

MTLJSONAdapter 会根据你传入的字典数据实例化合适的类。

NSDictionary *textMessage = @{
    @"id": @1,
    @"body": @"Hello World!"
};

NSDictionary *pictureMessage = @{
    @"id": @2,
    @"image_url": @"http://example.com/lolcat.gif"
};

XYTextMessage *messageA = [MTLJSONAdapter modelOfClass:XYMessage.class  fromJSONDictionary:textMessage error:NULL];

XYPictureMessage *messageB = [MTLJSONAdapter modelOfClass:XYMessage.class   fromJSONDictionary:pictureMessage error:NULL];

Mantle代码托管在:https://github.com/Mantle/Mantle

唱吧6.0版本使用Mantle后,据说:crash率比之前的版本有显示的降低,并且Mantle相关的crash占总crash的比率不到3%,点这里查看更多关于唱吧使用Mantle后的总结
本文在:http://wangyangyang.gitcafe.com/2014/11/04/用Mantle构建Model层/上面也有发表

相关文章

  • 用Mantle构建Model层

    在项目开发过程中,经常要自定义Model,然后在请求服务器得到数据后(一般是Json数据),用字典取值的方式给自定...

  • [iOS-Vendor] 集合

    AFNetworkingObjective-C 版本经典通用网络框架。 Mantle用于构建 Model 层,方便...

  • iOS开发常用的框架

    1. Mantle Mantle 让我们能简化 Cocoa 和 Cocoa Touch 应用的 model 层。简...

  • Whew Mantle

    What is Mantle Mantle是一个用于简化Model层的第三方库 Mantle effet 不想为M...

  • IOS之教你用Xtrace读懂Mantle源码

    Mantle是什么? Mantle makes it easy to write a simple model l...

  • [iOS-Vendor] Mantle

    结合数据层返回的 JSON 数据,Mantle 在基于 MVC 模式的应用的 Model 层中发挥了重要作用,包括...

  • Model--Mantle

    相比 MJExtension,Mantle 的使用还是有些烦琐,并且效率也并不占优势,但仍有很多可取之处。最近抽空...

  • Mantle的使用

    Mantle是基于KVO实现的,必须是属性才可以,普通的成员变量是无法使用Mantle的。 Model类需要集成M...

  • Mantle 使用之小坑坑

    最近工作中需要一个序列化Model的功能,于是发现了Mantle, Mantle 对于大多数iOS开发者来说可能...

  • Mantle源码学习笔记

    介绍一下Mantle吧,Mantle是应用于iOS中dictionary转为model的一个第三方库,功能强大,容...

网友评论

  • 巩固2022:后期向enum类型里面添加一个值,前期的项目会crash,这个怎么解决?谢谢
    巩固2022:@ch32053 使用枚举值表示状态,新添加枚举值时(新加状态),旧版本没有该新添加的枚举值,会导致程序闪退。已解决:
    ··· objc
    + (NSValueTransformer *)reviewStatusJSONTransformer {
    NSDictionary *status = @{
    @"pending": @(ZYSeviewStating),
    @"rejected": @(ZYSeviewStatusFail),
    @"authed": @(ZYSeviewStatusPass)
    };
    return [MTLValueTransformer transformerUsingForwardBlock:^id(id value, BOOL *success, NSError *__autoreleasing *error) {
    return status[value] ?: @(ZYSeviewUnknown);
    }];
    }
    ···
    ch32053:@巩庚 能不能具体点。。。
  • c5f8c453b41e:我有一个问题,当我在单元测试中使用Mantle时,会发生崩溃。字典转模型会失败,不知道你遇到过没。如果你们公司使用Mantle,在单元测试中应该也会遇到这个问题。希望回复下,非常感谢
  • walkerwzy:请问XYDateValueTransformerName是什么? 我能在源码里找到MTLURLValueTransformerName和MTLBooleanValueTransformerName, 这个xydate哪来的, 什么原理? 谢谢
    ch32053:@walkerwzy XYDateValueTransformerName 是作者的用法示例
  • 鸣2010:这个框架最烦的是需要继承他的类,这样做的第三方框架不是很好,还有使用起来也比较复杂,一堆协议,试着用mjextension吧。比这个好用
    ch32053:@鸣2010 如果不想继承,可以用YYModel,性能更好
  • ch32053:@孙春磊 好,下载看看,研究下
  • sclcoder:可以使用MJExtension,轻量级,简单易用。国产。
  • ch32053:@千煌89 这种情况下,需要自己给readedParentList赋值,类似与下面的代码
    + (NSValueTransformer *)readedParentListJSONTransformer {
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) {
    //在这里解析str,解析成Parent类array,返回array
    return array;
    } reverseBlock:^(NSArray *array) {
    //在这里解析array,解析Parent类array为字符串,返回string
    return str;
    }];
    }
  • 千煌89:当model里有对象数组的时候是怎么解决的,我试了几次都不能识别,
    比如
    @interface ZXAnnounceRead : BaseModel
    @property (nonatomic , strong) NSArray *readedParentList;
    @EnD
    readedParentList,里是Parent类

本文标题:用Mantle构建Model层

本文链接:https://www.haomeiwen.com/subject/dfcbtttx.html