Mantle Introduction
Mantle is a simple and efficient model-layer framework based on OBJECTIVE-C written under iOS and Mac platforms.
What can mantle do?
Mantle can easily convert the JSON data, dictionaries (Dictionary) and models (that is, objective objects) to each other, support custom mappings, and implement nscoding and nscoping built-in, greatly simplifying archive operations.
Why to use the mantle traditional model layer scheme encountered problems
What's the problem with the model layer that we write with objective-c usually?
We can use the Github API for example. Now suppose we want to show a Github Issue with objective-c, what should we do?
Now we can think of
Parse the JSON data dictionary directly and then show it to the UI
Convert JSON data to a model, assigning a value to the UI
About 1, there are many drawbacks, you can refer to my article: using the dictionary to the model in iOS development, now assume that we have selected 2, we will roughly define the following GHIssue
model:
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 {NSDATEFO Rmatter *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
You will see that there are many drawbacks to such a simple thing. There are even some other problems that are not shown in this example.
- The server's new data cannot be used to update this
GHIssue
- Cannot in turn
GHIssue
convert JSON
to
- For
GHIssueState
, if the enumeration is adapted, the existing archive will crash
- If the
GHIssue
interface changes, the existing archive will crash.
Using Mtlmodel
If we use Mtlmodel, we can declare that a class inherits from 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 = [[Nsdateformat Ter 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 "@" Assign EE ": @" Assignee "@" Updatedat ": @" Updated_at "}; } + (Nsvaluetransformer *) Urljsontransformer {return [Nsvaluetransformer Valuetransformerforname:mtlurlvaluetrans Formername]; } + (Nsvaluetransformer *) Htmlurljsontransformer {return [Nsvaluetransformer Valuetransformerforname:mtlurlvaluet Ransformername]; } + (Nsvaluetransformer *) Statejsontransformer {return [Nsvaluetransformer mtl_valuemappingtransformerwithdiction ary:@{@ "Open": @ (Ghissuestateopen), @ "closed": @ (ghissuestateclosed)}]; } + (Nsvaluetransformer *) Assigneejsontransformer {return [Mtljsonadapter Dictionarytransformerwithmodelclass:ghu Ser.class]; } + (NsvaluetransformeR *) Updatedatjsontransformer {return [Mtlvaluetransformer transformerusingforwardblock:^id (NSString *dateString, BOO L *success, Nserror *__autoreleasing *error) {return [self.dateformatter datefromstring:datestring]; } reverseblock:^id (NSDate *date, BOOL *success, Nserror *__autoreleasing *error) {return [Self.dateformatter str Ingfromdate:date]; }]; }-(Instancetype) Initwithdictionary: (nsdictionary *) Dictionaryvalue Error: (Nserror *) error {self = [super INITW Ithdictionary: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
It is clear that we do not need to achieve, <NSCoding>
<NSCopying>
-isEqual:
and -hash
. Within your subclass of life attributes, Mtlmodel can provide the default implementation of these methods.
The problems in the original example are well settled here.
Mtlmodel provides one - (void)mergeValueForKey:(NSString *)key fromModel:(id<MTLModel>)model{}
that can be integrated with any other model object that implements the Mtlmodel protocol.
+[MTLJSONAdapter JSONDictionaryFromModel:error:]
It can be similar to anything MTLJSONSerializing>``协议的对象转换成JSON字典,
that follows +[mtljsonadapter Jsonarrayfrommodels:error:] ", but the conversion is an array.
MTLJSONAdapter
fromJSONDictionary
and JSONDictionaryFromModel
can implement the transformation of the model and JSON.
JSONKeyPathsByPropertyKey
You can implement custom mappings for models and JSON.
JSONTransformerForKey
You can map different types of JSON and models.
classForParsingJSONDictionary
If you are using a class cluster (refer to: The application of the cluster in iOS development), Classforparsingjsondictionary allows you to choose which class to use for JSON deserialization.
- Mtlmodel can use a well-documented storage model without the need to implement boring nscoding protocols. The
-decodeValueForKey:withCoder:modelVersion:
method is automatically invoked when decoding, and can be easily customized if overridden.
Persistent mantle Mate archiving
The Mtlmodel protocol is implemented by default NSCoding
and can be NSKeyedArchiver
easily archived and reconciled with objects.
Mantle with Core Data
In addition to SQLite, Fmdb, if you want to perform complex queries in your data, handle many relationships, and support undo recovery, Core data is ideal.
However, this also brings some pain points:
- There are still a lot of drawbacks
Managed objects
to solve the above see some of the drawbacks, but core data self-birth also has his shortcomings. It requires a lot of line code to properly configure core data and get it.
- It is difficult to maintain correctness. Even experienced people make mistakes when using core data, and these problem frameworks cannot be solved.
If you want to get a JSON object, Core data needs to do a lot of work, but it can only get very little return.
However, if you have already used the core data,mantle in your app, it will still be a convenient conversion layer between your API and your managed model objects.
Mantle with Magicrecord (a core data framework)
Reference Magicalrecord Mate Mantle
The benefits mantle brings to us
Implements the Nscopying protocol, the subclass can direct copy is how cool thing
Realized the Nscoding protocol, say goodbye to Nsuserdefaults
Provides-isequal: And-hash default implementations, model as Nsdictionary key facilitates many
Support for custom mappings, which is useful in case of interface changes
Simple and do a good job, not doping network-related operations
Reasonable choice
Although the above said a series of benefits, but if your app's code size only tens of thousands of lines, or API only more than 10, or do not encounter these problems, suggest or not to introduce, kill chicken with nail knife is enough. However, the implementation and ideas of mantle are worth learning and drawing lessons from every iOS engineer.
Code
Https://github.com/terwer/MantleDemo
Reference
Https://github.com/mantle/mantle
http://segmentfault.com/a/1190000002431365
The use of the
Dictionary to model framework mantle: The iOS model most commonly used by foreign programmers