简介:mantle属于JSON转model的第三方库,利用mantle可以方便的利用JSON文件生成相应的model。作为一种被广泛使用的库,其具有以下优点:
1、字段映射比较方便。
2、扩展时比较方便。
3、可以实现很多复杂的映射关系和数值转换。
4、实现了NSCopying和NSCoding协议,可以轻松序列化。
强大的功能使得代码比较称重,在使用时的效率相比其它如YYModel,JSONModel等库有所欠缺。
首先JSON支持4中基本类型,包括字符串、数字、布尔型(true,false)、NULL;
同时支持两种结构:1、对象,即字典({"name":"zhangsan","age":23}),
- 2、数组,[1,2,3]。
这些类型和结构都和oc中的类型结构一一对应,可以进行相应的转换,但是值得注意的是mantle不支持不同类型的转换,如int转NSString。
- Mantle的使用方法
- 使用Mantle时需使我们自定义的model继承MTLModel类,并遵循MTLJSONSerializing协议,其中在MTLJSONSerializing协议中有一个@required和两个@optional方法
@required
+ (NSDictionary *)JSONKeyPathsByPropertyKey;
@optional:
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key;
+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary;
在JSONKeyPathsByPropertyKey方法中定义了JSON中的key与oc model中的key的这种一一对应的关系,返回值为一个字典,key为model中的property name,值为JSON中的key值。示例如下所示:
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
return @{
@"id" : @"id",
@"country" : @"country",
@"dialCode" : @"dialCode",
@"isInEurope" : @"isInEurope",
@"status" : @"status"
};
}
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key提供了一种关于属性名为key的转换方式:
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key
{
if ([key isEqualToString:@"id"]) {
return [MTLValueTransformer transformerUsingForwardBlock:
^id(NSNumber *value, BOOL *success, NSError **error)
{
NSString *temStr = [NSString stringWithFormat:@"%@",value];
return temStr;
}];
}
return nil;
}
方法内部会定义一个block来表示这种转换关系,这里只定义了前向转换,及从JSON转model,在这个block中,value来自JSON文件,属于JSON文件中的几种类型之一。
+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary方法主要解决要利用聚类的方式解析model的问题(用的比较少)。
另外在model中可能还含有字model,array,枚举等属性,这种情况下是需要我们自定义这种转换关系的,对于不用类型的属性,转换方式也不太一样,具体对应的转换方式如下:
类对象
+ (NSValueTransformer *)avatarDecorationModelJSONTransformer
{
return [MTLJSONAdapter dictionaryTransformerWithModelClass:[AWEUserAvatarDecotationModel class]];
}
数组对象
+ (NSValueTransformer *)followerDetailModelsJSONTransformer
{
return [MTLJSONAdapter arrayTransformerWithModelClass:[AWEUserFollowersDetailModel class]];
}
枚举对象
+ (NSValueTransformer *)genderJSONTransformer
{
NSDictionary *transformDictionary = @{[NSNull null] : @(AWEUserGenderTypeUnDefined),
@(0) : @(AWEUserGenderTypeMale),
@(1) : @(AWEUserGenderTypeFemale),
@(2) : @(AWEUserGenderTypeUnknown)};
return [NSValueTransformer mtl_valueMappingTransformerWithDictionary:transformDictionary
defaultValue:@(AWEUserGenderTypeUnDefined)
reverseDefaultValue:@"undefined"];
}
简单对象,如int转nsstring
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key
{
if ([key isEqualToString:@"id"]) {
return [MTLValueTransformer transformerUsingForwardBlock:
^id(NSNumber *value, BOOL *success, NSError **error)
{
NSString *temStr = [NSString stringWithFormat:@"%@",value];
return temStr;
}];
}
return nil;
}
值得注意的是,子model也必须继承mantle同时遵循MTLJSONSerializing协议。
dictionaryTransformerWithModelClass的具体实现如下所示:
+ (NSValueTransformer<MTLTransformerErrorHandling> *)dictionaryTransformerWithModelClass:(Class)modelClass {
NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLModel)]);
NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);
__block MTLJSONAdapter *adapter;
return [MTLValueTransformer
transformerUsingForwardBlock:^ id (id JSONDictionary, BOOL *success, NSError **error) {
if (JSONDictionary == nil) return nil;
if (![JSONDictionary isKindOfClass:NSDictionary.class]) {
if (error != NULL) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert JSON dictionary to model object", @""),
NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected an NSDictionary, got: %@", @""), JSONDictionary],
MTLTransformerErrorHandlingInputValueErrorKey : JSONDictionary
};
*error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
}
*success = NO;
return nil;
}
if (!adapter) {
adapter = [[self alloc] initWithModelClass:modelClass];
}
id model = [adapter modelFromJSONDictionary:JSONDictionary error:error];
if (model == nil) {
*success = NO;
}
return model;
}
reverseBlock:^ NSDictionary * (id model, BOOL *success, NSError **error) {
if (model == nil) return nil;
if (![model conformsToProtocol:@protocol(MTLModel)] || ![model conformsToProtocol:@protocol(MTLJSONSerializing)]) {
if (error != NULL) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert model object to JSON dictionary", @""),
NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected a MTLModel object conforming to <MTLJSONSerializing>, got: %@.", @""), model],
MTLTransformerErrorHandlingInputValueErrorKey : model
};
*error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
}
*success = NO;
return nil;
}
if (!adapter) {
adapter = [[self alloc] initWithModelClass:modelClass];
}
NSDictionary *result = [adapter JSONDictionaryFromModel:model error:error];
if (result == nil) {
*success = NO;
}
return result;
}];
}
上述方法中会定义两个block,分别指定了前向转换和后向转换的方法,其中前向转换中会调用
adapter = [[self alloc] initWithModelClass:modelClass];
id model = [adapter modelFromJSONDictionary:JSONDictionary error:error];
这就形成了一个循环,可以将子model进行转换。
当所有关于model的转换方式都定义好了以后,生成model是只需要调用下面一句话:
CountryModelBasedMTLModel *country2 = [MTLJSONAdapter modelOfClass:[CountryModelBasedMTLModel class] fromJSONDictionary:jsonDict error:&MTLError];
参数分别我们需要转换的对象类型以及JSON字典文件,其中个JSON字典的生成方式如下:
NSBundle *bundle = [NSBundle mainBundle];
NSString *jsonPath = [bundle pathForResource:@"Resource/JSON/country" ofType:@"json"];
NSData *data = [[NSData alloc] initWithContentsOfFile:jsonPath];
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
这个方法调用比较简单,内部调用了两个方法:
- MTLJSONAdapter *adapter = [[self alloc] initWithModelClass:modelClass];
- return [adapter modelFromJSONDictionary:JSONDictionary error:error];
在第一个方法中会生成一个MTLJSONAdapter的实例,里面包含了propertyKey和JSONkey的这种对应关系、JSON变量到property实例变量的这种值得转换关系等。具体代码如下所示:
- (id)initWithModelClass:(Class)modelClass {
NSParameterAssert(modelClass != nil);
NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);
self = [super init];
if (self == nil) return nil;
_modelClass = modelClass;
_JSONKeyPathsByPropertyKey = [modelClass JSONKeyPathsByPropertyKey];
NSSet *propertyKeys = [self.modelClass propertyKeys];
for (NSString *mappedPropertyKey in _JSONKeyPathsByPropertyKey) {
if (![propertyKeys containsObject:mappedPropertyKey]) {
NSAssert(NO, @"%@ is not a property of %@.", mappedPropertyKey, modelClass);
return nil;
}
id value = _JSONKeyPathsByPropertyKey[mappedPropertyKey];
if ([value isKindOfClass:NSArray.class]) {
for (NSString *keyPath in value) {
if ([keyPath isKindOfClass:NSString.class]) continue;
NSAssert(NO, @"%@ must either map to a JSON key path or a JSON array of key paths, got: %@.", mappedPropertyKey, value);
return nil;
}
} else if (![value isKindOfClass:NSString.class]) {
NSAssert(NO, @"%@ must either map to a JSON key path or a JSON array of key paths, got: %@.",mappedPropertyKey, value);
return nil;
}
}
_valueTransformersByPropertyKey = [self.class valueTransformersForModelClass:modelClass];
_JSONAdaptersByModelClass = [NSMapTable strongToStrongObjectsMapTable];
return self;
}
调用完上面方法后,所有的key对应关系以及值之间的转换关系都被存储在MTLJSONAdapter实例的属性中,最后在调用
[adapter modelFromJSONDictionary:JSONDictionary error:error];
此方法生成我们最终的oc实例对象,该方法的具体实现如下所示:
- (id)modelFromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error {
if ([self.modelClass respondsToSelector:@selector(classForParsingJSONDictionary:)]) {
Class class = [self.modelClass classForParsingJSONDictionary:JSONDictionary];
if (class == nil) {
if (error != NULL) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: NSLocalizedString(@"Could not parse JSON", @""),
NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"No model class could be found to parse the JSON dictionary.", @"")
};
*error = [NSError errorWithDomain:MTLJSONAdapterErrorDomain code:MTLJSONAdapterErrorNoClassFound userInfo:userInfo];
}
return nil;
}
if (class != self.modelClass) {
NSAssert([class conformsToProtocol:@protocol(MTLJSONSerializing)], @"Class %@ returned from +classForParsingJSONDictionary: does not conform to <MTLJSONSerializing>", class);
MTLJSONAdapter *otherAdapter = [self JSONAdapterForModelClass:class error:error];
return [otherAdapter modelFromJSONDictionary:JSONDictionary error:error];
}
}
NSMutableDictionary *dictionaryValue = [[NSMutableDictionary alloc] initWithCapacity:JSONDictionary.count];
for (NSString *propertyKey in [self.modelClass propertyKeys]) {
id JSONKeyPaths = self.JSONKeyPathsByPropertyKey[propertyKey];
if (JSONKeyPaths == nil) continue;
id value;
if ([JSONKeyPaths isKindOfClass:NSArray.class]) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
for (NSString *keyPath in JSONKeyPaths) {
BOOL success = NO;
id value = [JSONDictionary mtl_valueForJSONKeyPath:keyPath success:&success error:error];
if (!success) return nil;
if (value != nil) dictionary[keyPath] = value;
}
value = dictionary;
} else {
BOOL success = NO;
value = [JSONDictionary mtl_valueForJSONKeyPath:JSONKeyPaths success:&success error:error];
if (!success) return nil;
}
if (value == nil) continue;
@try {
NSValueTransformer *transformer = self.valueTransformersByPropertyKey[propertyKey];
if (transformer != nil) {
// Map NSNull -> nil for the transformer, and then back for the
// dictionary we're going to insert into.
if ([value isEqual:NSNull.null]) value = nil;
if ([transformer respondsToSelector:@selector(transformedValue:success:error:)]) {
id<MTLTransformerErrorHandling> errorHandlingTransformer = (id)transformer;
BOOL success = YES;
value = [errorHandlingTransformer transformedValue:value success:&success error:error];
if (!success) return nil;
} else {
value = [transformer transformedValue:value];
}
if (value == nil) value = NSNull.null;
}
dictionaryValue[propertyKey] = value;
} @catch (NSException *ex) {
NSLog(@"*** Caught exception %@ parsing JSON key path \"%@\" from: %@", ex, JSONKeyPaths, JSONDictionary);
// Fail fast in Debug builds.
#if DEBUG
@throw ex;
#else
if (error != NULL) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Caught exception parsing JSON key path \"%@\" for model class: %@", JSONKeyPaths, self.modelClass],
NSLocalizedRecoverySuggestionErrorKey: ex.description,
NSLocalizedFailureReasonErrorKey: ex.reason,
MTLJSONAdapterThrownExceptionErrorKey: ex
};
*error = [NSError errorWithDomain:MTLJSONAdapterErrorDomain code:MTLJSONAdapterErrorExceptionThrown userInfo:userInfo];
}
return nil;
#endif
}
}
id model = [self.modelClass modelWithDictionary:dictionaryValue error:error];
return [model validate:error] ? model : nil;
}
该方法主要范围三步,
1、根据property key获取JSON文件中的key,然后根据该key值获得JSON文件中的value。
2、根据property key获取transformer,所有的转换关系都存储在MTLJSONAdapter实例的一个属性中,该属性是一个字典,其中key为property key,值为transformer。
3、根据该transformer将JSON中的value转换为oc中的value,最后将结果存储在一个oc字典中。
执行完上述三步中你以为就完了吗,其实并没有,目前为止,我们只得到了一个字典,并没有生成我们需要的model实例,接下来会执行:
[self.modelClass modelWithDictionary:dictionaryValue error:error];
该方法内部的基本原理是利用KVC来对属性变量进行赋值,具体实现如下:
[obj setValue:validatedValue forKey:key];
值得注意的是在主体方法
[MTLJSONAdapter modelOfClass:[CountryModelBasedMTLModel class] fromJSONDictionary:jsonDict error:&MTLError];
内部会大量使用runtime来动态获取model的属性,如会采用
while (!stop && ![cls isEqual:MTLModel.class]) {
unsigned count = 0;
objc_property_t *properties = class_copyPropertyList(cls, &count);
cls = cls.superclass;
if (properties == NULL) continue;
@onExit {
free(properties);
};
for (unsigned i = 0; i < count; i++) {
block(properties[i], &stop);
if (stop) break;
}
}
来获取model中的属性等。