Mantle简介

Mantle是iOS和Mac平台下基于Objective-C编写的一个简单高效的模型层框架。

Mantle能做什么

Mantle可以轻松把JSON数据、字典(Dictionary)和模型(即Objective对象)之间的相互转换,支持自定义映射,并且内置实现了NSCoding和NSCoping,大大简化归档操作。

为什么要使用Mantle

传统的模型层方案遇到的问题

通常我们用Objective-C写的模型层遇到了什么问题?

我们可以用Github API来举例。现在假设我们想用Objective-C展现一个Github Issue,应该怎么做?

目前我们可以想到

  1. 直接解析JSON数据字典,然后展现给UI

  2. 将JSON数据转换为模型,在赋值给UI

关于1,弊端有很多,可以参考我的这篇文章:在iOS开发中使用字典转模型,现在假设我们选择了2,我们大致会定义下面的GHIssue模型:

GHIssue.h

    #import <Foundation/Foundation.h>typedef enum : NSUInteger {GHIssueStateOpen,GHIssueStateClosed} GHIssueState;@class GHUser;@interface GHIssue : NSObject <NSCoding, NSCopying>@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, copy, readonly) NSDate *updatedAt;@property (nonatomic, strong, readonly) GHUser *assignee;@property (nonatomic, copy, readonly) NSDate *retrievedAt;@property (nonatomic, copy) NSString *title;@property (nonatomic, copy) NSString *body;- (instancetype)initWithDictionary:(NSDictionary *)dictionary;@end

GHIssue.m

    #import "GHIssue.h"#import "GHUser.h"@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;}- (instancetype)initWithDictionary:(NSDictionary *)dictionary {self = [self init];if (self == nil) return nil;_URL = [NSURL URLWithString:dictionary[@"url"]];_HTMLURL = [NSURL URLWithString:dictionary[@"html_url"]];_number = dictionary[@"number"];if ([dictionary[@"state"] isEqualToString:@"open"]) {_state = GHIssueStateOpen;} else if ([dictionary[@"state"] isEqualToString:@"closed"]) {_state = GHIssueStateClosed;}_title = [dictionary[@"title"] copy];_retrievedAt = [NSDate date];_body = [dictionary[@"body"] copy];_reporterLogin = [dictionary[@"user"][@"login"] copy];_assignee = [[GHUser alloc] initWithDictionary:dictionary[@"assignee"]];_updatedAt = [self.class.dateFormatter dateFromString:dictionary[@"updated_at"]];return self;}- (instancetype)initWithCoder:(NSCoder *)coder {self = [self init];if (self == nil) return nil;_URL = [coder decodeObjectForKey:@"URL"];_HTMLURL = [coder decodeObjectForKey:@"HTMLURL"];_number = [coder decodeObjectForKey:@"number"];_state = [coder decodeIntegerForKey:@"state"];_title = [coder decodeObjectForKey:@"title"];_retrievedAt = [NSDate date];_body = [coder decodeObjectForKey:@"body"];_reporterLogin = [coder decodeObjectForKey:@"reporterLogin"];_assignee = [coder decodeObjectForKey:@"assignee"];_updatedAt = [coder decodeObjectForKey:@"updatedAt"];return self;}- (void)encodeWithCoder:(NSCoder *)coder {if (self.URL != nil) [coder encodeObject:self.URL forKey:@"URL"];if (self.HTMLURL != nil) [coder encodeObject:self.HTMLURL forKey:@"HTMLURL"];if (self.number != nil) [coder encodeObject:self.number forKey:@"number"];if (self.title != nil) [coder encodeObject:self.title forKey:@"title"];if (self.body != nil) [coder encodeObject:self.body forKey:@"body"];if (self.reporterLogin != nil) [coder encodeObject:self.reporterLogin forKey:@"reporterLogin"];if (self.assignee != nil) [coder encodeObject:self.assignee forKey:@"assignee"];if (self.updatedAt != nil) [coder encodeObject:self.updatedAt forKey:@"updatedAt"];[coder encodeInteger:self.state forKey:@"state"];}- (instancetype)copyWithZone:(NSZone *)zone {GHIssue *issue = [[self.class allocWithZone:zone] init];issue->_URL = self.URL;issue->_HTMLURL = self.HTMLURL;issue->_number = self.number;issue->_state = self.state;issue->_reporterLogin = self.reporterLogin;issue->_assignee = self.assignee;issue->_updatedAt = self.updatedAt;issue.title = self.title;issue->_retrievedAt = [NSDate date];issue.body = self.body;return issue;}- (NSUInteger)hash {return self.number.hash;}- (BOOL)isEqual:(GHIssue *)issue {if (![issue isKindOfClass:GHIssue.class]) return NO;return [self.number isEqual:issue.number] && [self.title isEqual:issue.title] && [self.body isEqual:issue.body];}

GHUser.h

    @interface GHUser : NSObject <NSCoding, NSCopying>@property (nonatomic, copy) NSString *login;@property (nonatomic, assign) NSUInteger id;@property (nonatomic, copy) NSString *avatarUrl;@property (nonatomic, copy) NSString *gravatarId;@property (nonatomic, copy) NSString *url;@property (nonatomic, copy) NSString *htmlUrl;@property (nonatomic, copy) NSString *followersUrl;@property (nonatomic, copy) NSString *followingUrl;@property (nonatomic, copy) NSString *gistsUrl;@property (nonatomic, copy) NSString *starredUrl;@property (nonatomic, copy) NSString *subscriptionsUrl;@property (nonatomic, copy) NSString *organizationsUrl;@property (nonatomic, copy) NSString *reposUrl;@property (nonatomic, copy) NSString *eventsUrl;@property (nonatomic, copy) NSString *receivedEventsUrl;@property (nonatomic, copy) NSString *type;@property (nonatomic, assign) BOOL siteAdmin;- (id)initWithDictionary:(NSDictionary *)dictionary;@end

你会看到,如此简单的事情却有很多弊端。甚至,还有一些其他问题,这个例子里面没有展示出来。

  1. 无法使用服务器的新数据来更新这个 GHIssue
  2. 无法反过来将 GHIssue 转换成 JSON
  3. 对于GHIssueState,如果枚举改编了,现有的归档会崩溃
  4. 如果 GHIssue 接口改变了,现有的归档会崩溃。

使用MTLModel

如果使用MTLModel,我们可以这样,声明一个类继承自MTLModel

    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@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",@"number": @"number",@"state": @"state",@"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 [MTLJSONAdapter dictionaryTransformerWithModelClass:GHUser.class];}+ (NSValueTransformer *)updatedAtJSONTransformer {return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *dateString, BOOL *success, NSError *__autoreleasing *error) {return [self.dateFormatter dateFromString:dateString];} reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) {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

很明显,我们不需要再去实现<NSCoding>, <NSCopying>, -isEqual:-hash。在你的子类里面生命属性,MTLModel可以提供这些方法的默认实现。

最初例子里面的问题,在这里都得到了很好的解决。

  • MTLModel提供了一个- (void)mergeValueForKey:(NSString *)key fromModel:(id<MTLModel>)model{},可以与其他任何实现了MTLModel协议的模型对象集成。

  • +[MTLJSONAdapter JSONDictionaryFromModel:error:]可以把任何遵循MTLJSONSerializing>``协议的对象转换成JSON字典,+[MTLJSONAdapter JSONArrayFromModels:error:]```类似,不过转换的是一个数组。

MTLJSONAdapter中的fromJSONDictionaryJSONDictionaryFromModel可以实现模型和JSON的相互转化。

JSONKeyPathsByPropertyKey可以实现模型和JSON的自定义映射。

JSONTransformerForKey可以对JSON和模型不同类型进行映射。

classForParsingJSONDictionary 如果你使用了类簇(关于类簇,请参考:类簇在iOS开发中的应用),classForParsingJSONDictionary可以让你选择使用哪一个类进行JSON反序列化。

  • MTLModel可以用归档很好的存储模型而不需要去实现令人厌烦的NSCoding协议。 -decodeValueForKey:withCoder:modelVersion:方法在解码时会自动调用,如果重写,可以方便的进行自定义。

持久化

Mantle配合归档

MTLModel默认实现了 NSCoding协议,可以利用NSKeyedArchiver方便的对对象进行归档和解档。

Mantle配合Core Data

除了SQLite、FMDB之外,如果你想在你的数据里面执行复杂的查询,处理很多关系,支持撤销恢复,Core Data非常适合。

然而,这样也带来了一些痛点:

  • 仍然有很多弊端Managed objects解决了上面看到的一些弊端,但是Core Data自生也有他的弊端。正确的配置Core Data和获取数据需要很多行代码。
  • 很难保持正确性。甚至有经验的人在使用Core Data时也会犯错,并且这些问题框架是无法解决的。

如果你想获取JSON对象,Core Data需要做很多工作,但是却只能得到很少的回报。

但是,如果你已经在你的APP里面使用了Core Data,Mantle将仍然会是你的API和你的managed model objects之间一个很方便的转换层。

Mantle配合MagicRecord(一个Core Data框架)

参考 MagicalRecord配合Mantle

Mantle为我们带来的好处

  • 实现了NSCopying protocol,子类可以直接copy是多么爽的事情

  • 实现了NSCoding protocol,跟NSUserDefaults说拜拜

  • 提供了-isEqual:和-hash的默认实现,model作NSDictionary的key方便了许多

  • 支持自定义映射,这在接口改变的情况下很有用

  • 简单且把一件事情做好,不掺杂网络相关的操作

合理选择

虽然上面说了一系列的好处,但如果你的App的代码规模只有几万行,或者API只有十几个,或者没有遇到上面这些问题, 建议还是不要引入了,杀鸡用指甲刀就够了。但是,Mantle的实现和思路是值得每位iOS工程师学习和借鉴的。

代码

https://github.com/terwer/MantleDemo

参考

https://github.com/mantle/mantle

http://segmentfault.com/a/1190000002431365

http://yyny.me/ios/Mantle%E3%80%81JSONModel%E3%80%81MJExtension%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/

PS: 本文由我们iOS122的小伙伴@TerwerGreen整理编辑,欢迎大家到他的个人博客terwer共同论道!

转载于:https://www.cnblogs.com/ios122/p/4895208.html

Mantle--国外程序员最常用的iOS模型字典转换框架相关推荐

  1. 国外程序员整理的 PHP 资源大全

    国外程序员整理的 PHP 资源大全 ziadoz 在 Github 发起维护的一个 PHP 资源列表,内容包括:库.框架.模板.安全.代码分析.日志.第三方库.配置工具.Web 工具.书籍.电子书.经 ...

  2. 学习C语言的教材、如何成为一名优秀的C程序员、激发程序员创意的6本书、国外程序员推荐:每个程序员都应读的书

    学习C语言的教材 我的C语言是自学的,这些年看过不少教材. 下面,我对其中一些教材做个点评. 1. How to Think Like a Computer Scientist: C version ...

  3. BAT 程序员们常用的开发工具

    阿里篇 一.Java 线上诊断工具 Arthas Arthas 是阿里巴巴 2018 年 9 月开源的一款 Java 线上诊断工具. 使用场景: 这个类从哪个 jar 包加载的?为什么会报各种类相关的 ...

  4. BAT程序员们常用的开发神器

    BAT程序员们常用的开发神器 黄小斜 今天 作者丨InfoQ编辑部 工欲善其事必先利其器,一个优秀的程序员除了代码写得好,善于利用各种开发工具同样可以事半功倍.以 BAT 为代表的各大厂程序员们在平时 ...

  5. 收藏!!BAT 程序员们常用的开发工具

    阅读本文大概需要 15 分钟. 作者:infoQ来源:https://tinyurl.com/y4wohyqt 工欲善其事必先利其器,一个优秀的程序员除了代码写得好,善于利用各种开发工具同样可以事半功 ...

  6. 6_程序员最常用的快捷键的都在这里啦 (哈哈,我不是)(20181208)

    程序员最常用的快捷键的都在这里啦 (哈哈,我不是) 1.windows上快捷键的使用 2.Ubuntu上快捷键的使用 3.Pycharm中快捷键的使用 4.Jupyter notebook快捷键使用 ...

  7. 文本编辑器_国外程序员最爱的5种文本编辑器

    文本编辑器的选择是很多初学编程者在学习编程时需要考虑的问题之一,当前IT行业应用开发平台软件较多,可供程序员选择的文本编辑器类型较多,但是一个好的文本编辑器能够提高程序工作的效率,达到事半功倍的效果. ...

  8. .NET 程序员十种常用辅助开发工具

     .NET 程序员十种常用辅助开发工具      一: .NET 程序员十种工具 - Visual Studio Converter(转换器工具) Visual Studio .NET 项目转换器(参 ...

  9. 国外程序员薪资曝光,美国最高,均年薪95879美元

    据CSDN发布的<2021-2022 中国开发者现状调查报告>显示,国内程序员的月薪主要集中在8K–17K这个范围,占49.2%,月薪在17K–30k的占20.5%. 2021年开发者月薪 ...

最新文章

  1. windows安装visual studio code并配置latex并编写latex论文
  2. 【Unity3D】资源对象、预设、查找对象、组合模式等知识点
  3. pythonis啥意思-Python基础:is和==的区别
  4. Ethermint部署及框架解析
  5. 备份k8s_树莓派k8s集群安装kafka集群及监控
  6. 用SQL Server(T-SQL)获取连接字符串
  7. 从leap的手掌发出射线,射线碰撞到物体,用红色的小球表示碰撞点,并把碰撞点用linerenderer渲染出来
  8. django 项目中使用项目环境制作脚本 通过终端命令运行脚本文件(management/commands)...
  9. StanfordDB class自学笔记 (10) Unified Modeling Language
  10. 手机锁屏密码忘了怎么办 锁屏密码破解法
  11. 我是如何创建学校免费上网账号上网的
  12. c++双向列表释放_SAIL-C-R-T4-A1-V0-B1温度控制仪-老友网
  13. python子图加标题_python – matplotlib的子图中的行和列标题
  14. 【python】自动登录51cto家园
  15. 【以太网硬件十七】什么!?网线边传数据还能边供电?
  16. 6.2、C++的内联函数、函数重载、局部变量和全局变量
  17. The Asset used by component in XXX scene “XXX.fire“ is missing. Detailed information:
  18. 联想y700台式计算机图片,细论联想Y700台式机的自我修养
  19. 迷你!方便!小程 序!
  20. Allegro自定义设置快捷键的三种方法

热门文章

  1. 专转本计算机第一章试题,江苏专转本 计算机第一章自测题(含答案).doc
  2. layui数据表格循环数据给特殊行变颜色遇到的bug
  3. 打开软件就遍历桌面和D盘的软件名通过UDP广播的方式发出。服务器监听指定的端口可以获得文件夹名称。不会暴露自己的IP
  4. 小谈Online-game服务器端设计(4)
  5. 图像放大 问题 即 二维数组放大
  6. c++ fmt 库安装和使用示例、clion配置
  7. 基于Mac环境搭建以太坊私有区块链进行挖矿模拟
  8. Android RecyclerView实现九宫格效果
  9. 富人和穷人的对比图,时刻提醒自己!
  10. 路考步骤七步走 科目三考试一定没问题!