概述 在iOS开发中,从服务器获取数据,然后解析成本地的模型是经常的要做事,而且重复次数特别多,每次增加一个网络请求或者增加一个模型都需要完成JSON
到Model
层的转换,手写通过字典valueForKey:
直接解析已经更不上时代的步伐,学会利用工具提高自己的工作效率,今天就分享一下Mantle
这个框架解析的心得。
Mantle
解决的痛点:
服务器经常更新(添加或者删除)字段,客服端需要在Model
层初始化的时候修改取值字段,易出错,而且繁琐。 实现自定义的Model
的序列化,以便将数据保存到本地,也就是说实现NSCoding
协议,在模型复制的情况下添加或者修改字段非常麻烦。 自定义的Model
的Copy
,你必须手动实现NSCopying
协议,而且没有办法反序列化成JSON
。 Mantle
很好的解决的以上痛点:
实现了NSCopying
协议。 实现了NSCoding
协议,可以通过NSKeyedArchiver
将数据归档到本地。 提供了isEqual:
和hash
的默认实现。 可以在Model
和JSON
之间互相转换。 以一个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
,birthday
和awesome
属性。假如一个属性在字典中被忽略,Mantle
将自动在JSON中查找带有相同名称的接口。
NSValueTransformer
Mantle
还能够处理任意类型的转换,例如NSString
heNSNumber
默认支持。然而,它也需要一些帮助对于处理非任意的类型例如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; }
Mantle
在runtime
调用这个方法来决定怎样变换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
是一个非常棒的补充处理JSON
APIs,然而,你也必须意识到假如你不得不处理非常复杂的数据或者不稳定的APIs它也是不合适的。
参考资料