io/badge/Carthage-compatibl
e-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[GITHUB](https://github.com/Mantle/Mantle/blob/master/README.md)
Mantle makes it easy to write a simple model layer for your Cocoa or Cocoa Touch
application.
## The Typical Model Object
What's wrong with the way model objects are usually written in Objective-C?
Let's use the [GitHub API](http://developer.github.com) for demonstration. How
would one typically represent a [GitHub
issue](http://developer.github.com/v3/issues/#get-a-single-issue) in
Objective-C?
```objc
typedef enum : NSUInteger {
GHIssueStateOpen,
GHIssueStateClosed
} GHIssueState;
@interface GHIssue : NSObject <NSCoding, NSCopying>
@property
@property
@property
@property
@property
@property
@property
@property
(nonatomic,
(nonatomic,
(nonatomic,
(nonatomic,
(nonatomic,
(nonatomic,
(nonatomic,
(nonatomic,
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_a
t"]];
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:@"assigne
e"];
if (self.updatedAt != nil) [coder encodeObject:self.updatedAt forKey:@"updat
edAt"];
[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
```
Whew, that's a lot of boilerplate for something so simple! And, even then, there
are some problems that this example doesn't address:
* There's no way to update a `GHIssue` with new data from the server.
* There's no way to turn a `GHIssue` _back_ into JSON.
* `GHIssueState` shouldn't be encoded as-is. If the enum changes in the future,
existing archives might break.
* If the interface of `GHIssue` changes down the road, existing archives might
break.
## Why Not Use Core Data?
Core Data solves certain problems very well. If you need to execute complex
queries across your data, handle a huge object graph with lots of relationships,
or support undo and redo, Core Data is an excellent fit.
It does, however, come with a couple of pain points:
* **There's still a lot of boilerplate.** Managed objects reduce some of the
boilerplate seen above, but Core Data has plenty of its own. Correctly
setting up a Core Data stack (with a persistent store and persistent store
coordinator) and executing fetches can take many lines of code.
* **It's hard to get right.** Even experienced developers can make mistakes
when using Core Data, and the framework is not forgiving.
If you're just trying to access some JSON objects, Core Data can be a lot of
work for little gain.
Nonetheless, if you're using or want to use Core Data in your app already,
Mantle can still be a convenient translation layer between the API and your
managed model objects.
## MTLModel
Enter
**[MTLModel](https://github.com/github/Mantle/blob/master/Mantle/MTLModel.h)**.
This is what `GHIssue` looks like inheriting from `MTLModel`:
```objc
typedef enum : NSUInteger {
GHIssueStateOpen,
GHIssueStateClosed
} GHIssueState;
@interface GHIssue : MTLModel <MTLJSONSerializing>
@property
@property
@property
@property
@property
@property
@property
(nonatomic,
(nonatomic,
(nonatomic,
(nonatomic,
(nonatomic,
(nonatomic,
(nonatomic,
+ (NSValueTransformer *)stateJSONTransformer {
return [NSValueTransformer mtl_valueMappingTransformerWithDictionary:@{
@"open": @(GHIssueStateOpen),
@"closed": @(GHIssueStateClosed)
}];
}
+ (NSValueTransformer *)assigneeJSONTransformer {
return [MTLJSONAdapter dictionaryTransformerWithModelClass:GHUser.class];
}
+ (NSValueTransformer *)updatedAtJSONTransformer {
return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *dateS
tring, BOOL *success, NSError *__autoreleasing *error) {
return [self.dateFormatter dateFromString:dateString];
} reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *er
ror) {
return [self.dateFormatter stringFromDate:date];
}];
}
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSErro
r **)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
```
Notably absent from this version are implementations of `<NSCoding>`,
`<NSCopying>`, `-isEqual:`, and `-hash`. By inspecting the `@property`
declarations you have in your subclass, `MTLModel` can provide default
implementations for all these methods.
The problems with the original example all happen to be fixed as well:
> There's no way to update a `GHIssue` with new data from the server.
`MTLModel` has an extensible `-mergeValuesForKeysFromModel:` method, which makes
it easy to specify how new model data should be integrated.
> There's no way to turn a `GHIssue` _back_ into JSON.
This is where reversible transformers really come in handy. `+[MTLJSONAdapter
JSONDictionaryFromModel:error:]` can transform any model object conforming to
`<MTLJSONSerializing>` back into a JSON dictionary. `+[MTLJSONAdapter
JSONArrayFromModels:error:]` is the same but turns an array of model objects int
o an JSON array of dictionaries.
> If the interface of `GHIssue` changes down the road, existing archives might b
reak.
`MTLModel` automatically saves the version of the model object that was used for
archival. When unarchiving, `-decodeValueForKey:withCoder:modelVersion:` will
NSDictionary *pictureMessage = @{
@"id": @2,
@"image_url": @"http://example.com/lolcat.gif"
};
XYTextMessage *messageA = [MTLJSONAdapter modelOfClass:XYMessage.class fromJSOND
ictionary:textMessage error:NULL];
XYPictureMessage *messageB = [MTLJSONAdapter modelOfClass:XYMessage.class fromJS
ONDictionary:pictureMessage error:NULL];
```
## Persistence
Mantle doesn't automatically persist your objects for you. However, `MTLModel`
does conform to `<NSCoding>`, so model objects can be archived to disk using
`NSKeyedArchiver`.
If you need something more powerful, or want to avoid keeping your whole model
in memory at once, Core Data may be a better choice.
## System Requirements
Mantle supports OS X 10.9+ and iOS 8.0+.
## Importing Mantle
To add Mantle to your application:
1. Add the Mantle repository as a submodule of your application's repository.
1. Run `script/bootstrap` from within the Mantle folder.
1. Drag and drop `Mantle.xcodeproj` into your application's Xcode project. Unfo
rtunately, an [Xcode bug](http://www.openradar.appspot.com/19676555) means you s
hould probably not add it to a workspace.
1. On the "General" tab of your application target, add `Mantle.framework` to t
he "Embedded Binaries".
[Carthage](https://github.com/Carthage/Carthage) users can simply add Mantle to
their `Cartfile`:
```
github "Mantle/Mantle"
```
If you would prefer to use [CocoaPods](http://cocoapods.org), there are some
[Mantle podspecs](https://github.com/CocoaPods/Specs/tree/master/Specs/Mantle) t
hat
have been generously contributed by third parties.
If you
## License
Mantle is released under the MIT license. See
[LICENSE.md](https://github.com/github/Mantle/blob/master/LICENSE.md).
## More Info
Have a question? Please [open an issue](https://github.com/Mantle/Mantle/issues/
new)!