使用Mantle高效构建Model



概述



在iOS开发中,从服务器获取数据,然后解析成本地的模型是经常的要做事,而且重复次数特别多,每次增加一个网络请求或者增加一个模型都需要完成JSONModel层的转换,手写通过字典valueForKey:直接解析已经更不上时代的步伐,学会利用工具提高自己的工作效率,今天就分享一下Mantle这个框架解析的心得。



Mantle解决的痛点:




  • 服务器经常更新(添加或者删除)字段,客服端需要在Model层初始化的时候修改取值字段,易出错,而且繁琐。


  • 实现自定义的Model的序列化,以便将数据保存到本地,也就是说实现NSCoding协议,在模型复制的情况下添加或者修改字段非常麻烦。


  • 自定义的ModelCopy,你必须手动实现NSCopying协议,而且没有办法反序列化成JSON



Mantle很好的解决的以上痛点:




  • 实现了NSCopying协议。


  • 实现了NSCoding协议,可以通过NSKeyedArchiver将数据归档到本地。


  • 提供了isEqual:hash的默认实现。


  • 可以在ModelJSON之间互相转换。



以一个CATProfile模型类为例,演示这个框架怎样以一种非常简单的方式将一个NSDictionary对象映射成一个Objective-C,反之亦然。



下面的CATProfile模型















1
2
3
4
5
6
7
8
9


{
"id": 1,
"name": "Objective Cat",
"birthday": "2013-09-12 13:29:36 +0100",
"website": "http://objc.at",
"location": { "lat": "48.2083", "lon": "16.3731" },
"relationship_status": "single",
"awesome": true
}





下面我们创建一个MTLModel子类代表以上的Json对象。















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18


// CATProfile.h
typedef NS_ENUM(NSInteger, CATRelationshipStatus) {
CATRelationshipStatusSingle = 0,
CATRelationshipStatusInRelationship,
CATRelationshipStatusComplicated
};

@interface CATProfile : MTLModel<MTLJSONSerializing>

@property(strong, nonatomic) NSNumber profileId;
@property(strong, nonatomic) NSString
name;
@property(strong, nonatomic) NSDate birthday;
@property(strong, nonatomic) NSURL
website;
@property(nonatomic) CLLocationCoordinate2D locationCoordinate;
@property(nonatomic) CATRelationshipStatus relationshipStatus;
@property(nonatomic, getter=isAwesome) BOOL awesome;

@end





CATProfile类继承自MTLModel并且实现了MTLJSONSerializing,协议要求实现+JSONKeyPathsByPropertyKey方法















1
2
3
4
5
6
7
8
9
10
11
12
13
14


// CATProfile.m
@implementation

+ (NSDictionary )JSONKeyPathsByPropertyKey {
// properties defined in header < : > key in JSON Dictionary
return @{
@"profileId": @"id",
@"websiteURL": @"website",
@"locationCoordinate": @"location",
@"relationshipStatus": @"relationship_status",
};
}

@end





+JSONKeyPathsByPropertyKey方法返回一个在JSON数据中需要模型的属性匹配的值的字典,这能确保Mantle知道那个JSON键键使用来构成一个指定的模型接口。



很明显除了这个列表中的name,birthdayawesome属性。假如一个属性在字典中被忽略,Mantle将自动在JSON中查找带有相同名称的接口。



NSValueTransformer



Mantle还能够处理任意类型的转换,例如NSStringheNSNumber默认支持。然而,它也需要一些帮助对于处理非任意的类型例如NSURL枚举还有自定义的结构体像CLLocationCoordinate2D



Mantle依赖Foundation框架下NSValueTransformer对象来实现在模型代表的JSON层和OC对象的实际接口之间的值的映射。



创建一个自定义transformer给某个属性,我们需要实现一个叫做+<propertyName>JSONTransformer的类方法并且返回一个想要的NSValueTransformer对象。















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


// mapping birthday to NSDate and vice-versa
+ (NSValueTransformer )birthdayJSONTransformer {
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString dateString) {
return [self.dateFormatter dateFromString:dateString];
} reverseBlock:^(NSDate
date) {
return [self.dateFormatter stringFromDate:date];
}];
}

+ (NSDateFormatter )dateFormatter {
NSDateFormatter
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";

return dateFormatter;
}





Mantleruntime调用这个方法来决定怎样变换birthday 属性,正向转换block从一个字符串对象到一个NSDate对象,反向转换block将NSDate对象转换回一个字符串对象,非常棒!



下面列举了一些变换方法,对于所有的我们需要注意的非任意属性以供参考。



NSURL ↔︎ JSON string















1
2
3


+ (NSValueTransformer )websiteURLJSONTransformer {
return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
}





CLLocationCoordinate2D ↔︎ JSON object















1
2
3
4
5
6
7
8
9
10


+ (NSValueTransformer )locationCoordinateJSONTransformer {
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSDictionary coordinateDict) {
CLLocationDegrees latitude = [coordinateDict[@"lat"] doubleValue];
CLLocationDegrees longitude = [coordinateDict[@"lon"] doubleValue];
return [NSValue valueWithMKCoordinate:CLLocationCoordinate2DMake(latitude, longitude)];
} reverseBlock:^(NSValue
coordinateValue) {
CLLocationCoordinate2D coordinate = [coordinateValue MKCoordinateValue];
return @{@"lat": @(coordinate.latitude), @"lon": @(coordinate.longitude)};
}];
}





enum ↔︎ JSON string















1
2
3
4
5
6
7


+ (NSValueTransformer )relationshipStatusJSONTransformer {
return [NSValueTransformer mtl_valueMappingTransformerWithDictionary:@{
@"single": @(CATRelationshipStatusSingle),
@"relationship": @(CATRelationshipStatusInRelationship),
@"complicated": @(CATRelationshipStatusComplicated)
}];
}





BOOL ↔︎ JSON boolean















1
2
3


+ (NSValueTransformer )awesomeJSONTransformer {
return [NSValueTransformer valueTransformerForName:MTLBooleanValueTransformerName];
}





从JSON创建模型对象



一旦模型配置完成,就该获得JSON对象从API中,并且把它转换成我们模型的一个实例。首先,我们需要把JSON呈现转换成一个NSDictionary,它能够用来通过Mantle来创建我们的模型,很幸运的是iOS提供了一个非常棒的方法来处理它,就是通过NSJSONSerialization



那样以后,MTLJSONAdapter类就能利用Mantle做繁重的工作来创建我们的模型。















1
2
3
4
5
6


// create NSDictionary from JSON data
NSData JSONData = … // the JSON response from the API
NSDictionary JSONDict = [NSJSONSerialization JSONObjectWithData:JSONData options:0 error:NULL];

// create model object from NSDictionary using MTLJSONSerialisation
CATProfile
profile = [MTLJSONAdapter modelOfClass:CATProfile.class fromJSONDictionary:JSONDict error:NULL];





从模型对象创建JSON



MTLJSONAdapter也有能力创建一个字典从我们的的模型类中,以便能直接编码回一个JSON字符串。















1
2
3
4
5
6


// create NSDictionary from model class using MTLJSONSerialisation
CATProfile profile = …
NSDictionary
profileDict = [MTLJSONAdapter JSONDictionaryFromModel:profile];

// convert NSDictionary to JSON data
NSData JSONData = [NSJSONSerialization dataWithJSONObject:profileDict options:0 error:NULL];






假如当创建一个JSON呈现你的模型是在你的模型中有一个属性不应该包括,你应该返回NSNull.null,例如。在+JSONKeyPathsByPropertyKey的@{“name” : “NSNull.null”}。Mantle将安全的忽略这个属性。




映射数组和字典



大多数情况,模型和其他模型有关系,这些关系普遍通过JSON数组或者对象来呈现。















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


{
"id": 1,
"name": "Objective Cat",
…,

"owner": {
"id": 99,
"name": "Alexander Schuch"
},
"friends": [
{
"name": "Owly",
"type": "bird"
},
{
"name": "Hedgy",
"type": "mammal"
}
]
}





Mantle支持映射这些关系到新的模型,为了让Mantle知道怎样变换关系,我们可以使用下面提供的分类方法中的一个来返回NSValueTransformer















1
2


+ (NSValueTransformer )mtl_JSONDictionaryTransformerWithModelClass:(Class)modelClass;
+ (NSValueTransformer )mtl_JSONArrayTransformerWithModelClass:(Class)modelClass;





当然Mantle需要知道这些官和他们的将要转换的MTLModel子类,就像和创建一个新的MTLModel子类并且实现将要映射到这些对象的MTLJSONSerializing协议一样简单。然后我们可以添加一些新的属性到我们CATProfile类中并且实现两个新的转换器。















1
2
3
4
5
6
7
8
9
10
11
12


// CATProfile.h
@property(strong, nonatomic) CATOwner owner; // CATOwner is a MTLModel subclass
@property(strong, nonatomic) NSArray friends; // Array of CATFriend objects

// CATProfile.m
+ (NSValueTransformer
)ownerJSONTransformer {
return [NSValueTransformer mtl_JSONDictionaryTransformerWithModelClass:CATOwner.class];
}

+ (NSValueTransformer )friendsJSONTransformer {
return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:CATFriend.class];
}





一些有用的补充



我们简单的聊了一下NSValueTransformer在前面,NSValueTransformer有一个非常棒的特征让通过名字全局注册一个变换器成为可能。假如你正在使用相同的变换器在你整个app中,确保子类化NSValueTransformer,注册你的自定义变换器一次并且在你随后的MTLModels中使用它。















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


// In CATProfile.m
NSString const kCATCustomValueTransformerName = @"CATCustomValueTransformer";

+ (void)initialize
{
// Register NSValueTransformer
if (self == CATProfile.class) {
CATCustomValueTransformer transformer = [CATCustomValueTransformer new];
[NSValueTransformer setValueTransformer:transformer forName:kCATCustomValueTransformerName];
}
}

// Then use the custom transformer to translate properties using Mantle
+ (NSValueTransformer
)whateverPropertyJSONTransformer {
return [NSValueTransformer valueTransformerForName:kCATCustomValueTransformerName];
}





结论



Mantle是一个非常棒的补充处理JSONAPIs,然而,你也必须意识到假如你不得不处理非常复杂的数据或者不稳定的APIs它也是不合适的。



参考资料



热评文章