注册

模型处理工具不仅仅只有YYModel,还有更强的Mantle

Mantle 使为您的 Cocoa 或 Cocoa Touch 应用程序编写简单的模型层变得容易

Let's use the GitHub API for demonstration~!

typedef enum : NSUInteger {
GHIssueStateOpen,
GHIssueStateClosed
} GHIssueState;

@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;

- (id)initWithDictionary:(NSDictionary *)dictionary;

@end


typedef enum : NSUInteger {
GHIssueStateOpen,
GHIssueStateClosed
} GHIssueState;

@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;

- (id)initWithDictionary:(NSDictionary *)dictionary;

@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;
}

- (id)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;
}

- (id)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 decodeUnsignedIntegerForKey:@"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 encodeUnsignedInteger:self.state forKey:@"state"];
}

- (id)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];
}

@end

哇,对于这么简单的事情来说,这是很多样板!而且,即便如此,这个例子也没有解决一些问题:

  • 无法GHIssue使用来自服务器的新数据更新 a 
  • 没有办法将 a 变GHIssue JSON。
  • GHIssueState不应按原样编码。如果将来枚举更改,现有存档可能会中断。
  • 如果未来的界面发生GHIssue变化,现有的档案可能会中断。


为什么不使用核心数据?

Core Data 很好地解决了某些问题。如果您需要对数据执行复杂的查询,处理具有大量关系的巨大对象图,或支持撤消和重做,Core Data 非常适合。

然而,它确实有几个痛点:

  • 仍然有很多样板。托管对象减少了上面看到的一些样板,但 Core Data 有很多自己的样板。正确设置 Core Data 堆栈(带有持久存储和持久存储协调器)并执行提取可能需要多行代码。
  • 很难做对。即使是有经验的开发人员在使用 Core Data 时也可能会犯错误,而且该框架是不可原谅的。

如果您只是想访问一些 JSON 对象,那么 Core Data 可能会做大量工作,但收获甚微。

尽管如此,如果您已经在应用程序中使用或想要使用 Core Data,Mantle 仍然可以作为 API 和托管模型对象之间的方便转换层。


MTL模型

输入 MTLModelGHIssue看起来像继承自MTLModel

typedef enum : NSUInteger {
GHIssueStateOpen,
GHIssueStateClosed
} GHIssueState;

@interface GHIssue : MTLModel

@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


值得注意的是,从这个版本是缺席的实现, -isEqual:,和-hash通过检查@property 您在子类中声明,MTLModel可以为所有这些方法提供默认实现。

原始示例的问题也都被修复了:

无法GHIssue使用来自服务器的新数据更新 a 

MTLModel有一个可扩展的-mergeValuesForKeysFromModel:方法,可以很容易地指定应该如何集成新的模型数据。

没有办法将 a 变GHIssue JSON。

这是可逆变压器真正派上用场的地方。+[MTLJSONAdapter JSONDictionaryFromModel:error:]可以将任何符合 的模型对象转换 回 JSON 字典。+[MTLJSONAdapter JSONArrayFromModels:error:]是相同的,但将模型对象数组转换为字典的 JSON 数组。

如果未来的界面发生GHIssue变化,现有的档案可能会中断。

MTLModel自动保存用于存档的模型对象的版本。取消归档时,-decodeValueForKey:withCoder:modelVersion:如果被覆盖将被调用,为您提供一个方便的挂钩来升级旧数据。


Mantle 不会自动为您保留对象。但是,MTLModel 确实符合,因此可以使用 将模型对象存档到磁盘 NSKeyedArchiver

如果你需要更强大的东西,或者想要避免将整个模型一次保存在内存中,Core Data 可能是更好的选择。

Carthage

github "Mantle/Mantle"


CocoaPods

target 'MyAppOrFramework' do
pod 'Mantle'
end


demo下载及常见问题:https://github.com/Mantle/Mantle

源码:Mantle-master.zip



0 个评论

要回复文章请先登录注册