Simplify iOS Models With Mantle – An Intro

Mantle is a framework that makes using iOS models ridiculously easy. It helps manage all the boilerplate associated with models including serialization, deserialization, and equality.

Let’s say we have an application that needs to create a user and it must contact a server to do so. Also, let’s say the server is written in Ruby.

  1. Create the Model
  2. First, we need a User model to pass to the server. Here is what that model might look like:

    @interface EKOCloudAPIProfile : NSObject
    
    @property (strong, nonatomic, readwrite) NSString *firstName;
    @property (strong, nonatomic, readwrite) NSString *lastName;
    @property (strong, nonatomic, readwrite) NSNumber *age;
    @property (strong, nonatomic, readwrite) NSNumber *weight;
    @property (strong, nonatomic, readwrite) NSDate *birthdate;
    
    - (BOOL)isEqual:(id)anObject;
    - (void)hash;
    
    

    If we want to test our model later (of course we do), we should also implement -isEqual:, which of course requires us to also implement -hash. Yay, more boilerplate code! It is really going to get tedious implementing that -isEqual: method with all of the properties we have.

    Luckily, Mantle has MTLModel. All of our models should inherit from MTLModel. Oh, by the way, MTLModel implements -isEqual:, -hash, and all of the methods from NSCopying for us. Here is our MTLModel:

    @interface EKOCloudAPIProfile : MTLModel 
    
    @property (strong, nonatomic, readwrite) NSString *firstName;
    @property (strong, nonatomic, readwrite) NSString *lastName;
    @property (strong, nonatomic, readwrite) NSNumber *age;
    @property (strong, nonatomic, readwrite) NSNumber *weight;
    @property (strong, nonatomic, readwrite) NSDate *birthdate;
    
    @end
    

  3. Set Up Property Keys
  4. Now that we have our MTLModel, we need to implement the only required method; +JSONKeyPathsByPropertyKey. This method is responsible for serializing variables in the casing the server wants them, and deserializes them into the properties we want. It’ll look something like this:

    + (NSDictionary)JSONKeyPathByPropertyKey {
        return @{
            @"firstName" : @"first_name",
            @"lastName" : @"last_name"
        };
    }
    

    Since age, weight, and birthdate will look the same on both sides, we can emit them from the dictionary.

  5. Add Property Transformations
  6. If we stopped here, our serialized JSON would contain an NSDate object for the birthdate. Another goodie from Mantle is custom JSON transformer methods. We can add custom serialization or deserialization for any property by using +<key>JSONTransformer.

    We really want to serialize the birthdate property into a string, and we would expect the server to send us a string that we will then deserialize back into an NSDate. Here is our implementation:

    + (NSValueTransformer *)birthdateJSONTransformer 
        dateFormatter = [[NSDateFormatter alloc] init];
        dateFormatter.dateFormat = @"yyyy/MM/dd";
    
        return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *dateStr) {
            return [dateFormatter dateFromString:dateStr];
        } reverseBlock:^(NSDate *date) {
            return [dateFormatter stringFromDate:date];
        }];
    }
    

    Using +reversibleTransformerWithForwardBlock:reverseBlock: from MTLValueTransformer, we can serialize a property with forwardBlock and deserialize with reverseBlock.

  7. Use the Model
  8. Now that our model is looking all pretty, it is time to actually use it!

    When we want to serialize our model to send in a request we use [MTLJSONAdapter +JSONDictionaryFromModel:].

    When we want to deserialize our model from a response we use [MTLJSONAdapter +modelOfClass:fromJSONDictionary:error:].

    Here is what our network layer might look like:

    - (User)updateUser:(User)user {
        NSDictionary *userDict = [MTLJSONAdapter JSONDictionaryFromModel:user];
        NSDictionary *updatedUser = [self.webClient postToPath:@"users.json" withParameters:userDict];
    
        return [MTLJSONAdapter modelOfClass:[User class] fromJSONDictionary:updatedUser error:nil];
    }
    

And that’s it! We have a model that can be easily serialized, deserialized, and compared to other objects. Be sure to checkout Mantle’s Github page for plenty more treats.
 

Conversation
  • Byron says:

    Hi Ryan,

    Nice post! Thanks.

    Question…is there a way with Mantle to specify certain properties that should not be serialized to JSON?

    The situation would be transient properties on the object that don’t get persisted back to the server, such as ‘distance’ when dealing with a geolocation. You may want to have a distance property that represents the users distance from their current location to the target location. You wouldn’t want to store that on the server, though.

    Thanks!
    Byron

    • Ryan Abel Ryan Abel says:

      Hi Byron,

      Great question. There is a way to specify that a particular property should not be serialized to JSON. All you need to do is set the value of that property in JSONKeyPathsByPropertyKey to null. For example:

      + (NSDictionary *)JSONKeyPathsByPropertyKey {
          return @{
              @"distance": [NSNull null]
          };
      }
      
  • Byron says:

    Perfect…thanks!

  • Chinthaka Kumarasiri says:

    Hi Ryan,

    Really helpful. Thanks.

    I have a question. How do you populat the ‘User’ model object from core data for following line?

    [MTLJSONAdapter +JSONDictionaryFromModel:]

    Mantle documentation tells we can use this.
    NSError *error = nil;
    XYUser *user = [MTLJSONAdapter modelOfClass:XYUser.class fromJSONDictionary:JSONDictionary error:&error];

    Again I have to know the ‘JSONDictionary’ for this.

    Thanks
    Chinthaka

  • ckoskar says:

    + (NSDictionary)JSONKeyPathByPropertyKey {

    you forgot to add * sign

  • Dipesh says:

    I have a RFQDetails class for which there are multiple lineitemdetails of class LineItemDetails:
    @interface RFQDetails : MTLModel
    @property (nonatomic,strong) NSNumber *approx_order_value;
    @property (nonatomic,strong) NSString *rfqno;
    @property (nonatomic,strong) NSOrderedSet *lineitemdetails;
    @end

    @interface LineItemDetails : MTLModel
    @property (nonatomic,strong) NSNumber *quantity;
    @property (nonatomic,strong) NSString *itemName;
    @end

    Now, for each rfqdetails there are multiple lineitemdetails. So it is a one to many relationship.
    I created the relationship with coredata entities and defined the relationshipModelClassesByPropertyKey methods.

    What is the best way to insert rfqdetails (along with lineitemdetails) from mantle to core data?

  • Savankumar says:

    How can I Update my model :

    First i get MT model can get data.
    Then second time fetch data from web service NULL come. Even no error found in
    [MTLJSONAdapter modelOfClass:CTSignUpRespose.class fromJSONDictionary:[response dictionaryWithoutNulls] error:&error1];
    erro1 get null and whole object get null with keys

  • Comments are closed.