Mantle使用说明
Mantle
已经在很多的iOS APP 中使用了,根据github上的文档,我们也能大概看明白其用途和使用方法,但是我在看的过程中也饶了一点弯路,所以特意记录了下来,(勿喷)
1. Mantle 是json <-> Model 的便捷工具库
2. Mantle 对
在看官方提供的文档中,我尝试着写这样的模型,此模型是参照网上另外一篇博客 链接
@interface SysModel:MTLModel<MTLJSONSerializing>
/**
"type": 1,
"id": 7405,
"message": 0.0126,
"country": "CN",
"sunrise": 1461791897,
"sunset": 1461841609
*/
@property (nonatomic,copy) NSString * sys_type;
@property (nonatomic,copy) NSString * sys_id;
@property (nonatomic,copy) NSString * sys_message;
@property (nonatomic,copy) NSString * sys_country;
@property (nonatomic,copy) NSString * sys_sunrise;
@property (nonatomic,copy) NSString * sys_sunset;
@end
@interface WeatherModel : MTLModel<MTLJSONSerializing>
/**
*
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01d"
*/
@property (nonatomic,copy) NSString * weather_id;
@property (nonatomic,copy) NSString * weather_main;
@property (nonatomic,copy) NSString * weather_description;
@property (nonatomic,copy) NSString * weather_icon;
@end
@interface FirstInterfaceModel : MTLModel <MTLJSONSerializing>
@property (nonatomic,strong) NSDate *date;
@property (nonatomic,strong) NSNumber *humidity;
@property (nonatomic,strong) NSNumber *temperature;
@property (nonatomic,strong) NSString *cod;
@property (nonatomic,strong) NSString *name;
// xxxTestStr 在
@property (nonatomic,strong) NSString *xxxTestStr;
@property (nonatomic,strong) NSArray *weathers;//
@property (nonatomic,copy) NSString * country;
@property (nonatomic,strong) SysModel *sys;
@property (nonatomic,strong) NSArray *datas;
@end
/*
{
base = stations;
clouds = {
all = 0;
};
cod = 200;
coord = {
lat = "39.98";
lon = "116.3";
};
dt = 1461836507;
id = 1809104;
main = {
humidity = 33;
pressure = 1011;
temp = "299.03";
"temp_max" = "302.04";
"temp_min" = "297.15";
};
name = Haidian;
sys = {
country = CN;
id = 7405;
message = "0.01";
sunrise = 1461791894;
sunset = 1461841611;
type = 1;
};
visibility = 10000;
weather = (
{
description = "clear sky";
icon = 01d;
id = 800;
main = Clear;
}
);
wind = {
deg = 150;
speed = 6;
};
}
*/
我们可以注意到FirstInterfaceModel
中包含array, Date,SysModel
等类型,我们在映射时改则默默做呢,继续往下看
所有的Model 都要遵循这个协议
@protocol MTLJSONSerializing <MTLModel>
看过官方文旦的同学都应该知道该协议提供了哪些便利的方法
+ (NSDictionary *)JSONKeyPathsByPropertyKey;
就是这个方法,将model的属性和json的keyPath 进行了映射。在FirstInterfaceModel
是这样实现的。
NSMutableDictionary *mutDic = [[NSDictionary mtl_identityPropertyMapWithModel:[self class]] mutableCopy];
/*
lldb) po mutDic
{
arrWeathers = arrWeathers;
cod = cod;
date = date;
humidity = humidity;
name = name;
sys = sys;
temperature = temperature;
xxxTestStr = xxxTestStr;
}
*/
[mutDic setObject:@"dt"forKey:@"date"];
[mutDic setObject:@"main.humidity"forKey:@"humidity"];
[mutDic setObject:@"main.temp"forKey:@"temperature"];
[mutDic setObject:@"weather"forKey:@"weathers"];
return mutDic;
先根据属性获取唯一的键值对,然后进行替换,有同学不禁会问,干嘛要替换啊,因为服务端返回的json的key和我们自定义的property的名字是不一样的啊,所以在这个方法中我们有机会去修改。另外main.temp
是啥东东? 嗯,这个是所谓的keyPath啊,json的keypath。如main对应一个Dictionary 的key,value是其字典,temp 又是字典中的一个key,通过这个keypath 就可以获取其值了。
还有Weathers
对应的可是数组啊,我们想数组里面存储的是WeatherModel
的对象,date
存储的是NSDate
的对象等,这该咋办呢?嗯,好办的很。
// 转换为weatherModel数组
+ (NSValueTransformer *)weathersJSONTransformer {
//NSLog(@"arrWeather = %@",[MTLJSONAdapter arrayTransformerWithModelClass:WeatherModel.class]);
return [MTLJSONAdapter arrayTransformerWithModelClass:WeatherModel.class];
}
// 转换为SysModel
+ (NSValueTransformer *)sysJSONTransformer {
return [MTLJSONAdapter dictionaryTransformerWithModelClass:SysModel.class];
}
// 转换为datas数组
+ (NSValueTransformer*)datasJsonTransformer{
return [MTLJSONAdapter arrayTransformerWithModelClass:[NSString class]];
}
+ (NSValueTransformer *)dateJSONTransformer {
return [MTLValueTransformer transformerUsingForwardBlock:^id(id value,BOOL *success, NSError*__autoreleasing *error) {
NSNumber *dateNum = (NSNumber *)value;
return [NSDate dateWithTimeIntervalSince1970:dateNum.floatValue];
} reverseBlock:^id(id value,BOOL *success, NSError *__autoreleasing *error) {
NSDate *numDate = (NSDate *)value;
return [NSString stringWithFormat:@"%f", [numDate timeIntervalSince1970]];
}];
}
好吧,看的很爽是吧,自己敲一遍代码,看看
NSURL *url = [NSURL URLWithString:@"http://api.openweathermap.org/data/2.5/weather?lat=40.101159&lon=116.275260&appid=e85d42a3899e3566035a1455f3f84cea"];
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:url]
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse* response,NSData* data, NSError* connectionError){
if (!connectionError) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
//将JSON数据和Model的属性进行绑定
NSMutableDictionary * dc = [[NSMutableDictionary alloc] initWithDictionary:dict];
[dc setObject:@[@"1",@"2",@"3"] forKey:@"datas"];
[Xtrace traceBundleContainingClass:[MTLJSONAdapter class]];
FirstInterfaceModel *model = [MTLJSONAdapter modelOfClass:[FirstInterfaceModel class] fromJSONDictionary:dc error:nil];
NSError *testError =nil;
NSDictionary *dicFromModel = [MTLJSONAdapter JSONDictionaryFromModel:model error:&testError];
}
}];
@implementation SysModel
@property (nonatomic,copy) NSString * sys_type;
@property (nonatomic,copy) NSString * sys_id;
@property (nonatomic,copy) NSString * sys_message;
@property (nonatomic,copy) NSString * sys_country;
@property (nonatomic,copy) NSString * sys_sunrise;
@property (nonatomic,copy) NSString * sys_sunset;
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return @{@"sys_id":@"id",@"sys_type":@"type",@"sys_message":@"message",@"sys_country":@"country",@"sys_sunrise":@"sunrise",@"sys_sunset":@"sunset"};
}
@end
@implementation WeatherModel
+(NSDictionary *)JSONKeyPathsByPropertyKey {
return @{@"weather_id":@"id",@"weather_main":@"main",@"weather_description":@"description",@"weather_icon":@"icon"};
}
运行后发现 网络请求回来进行转换,打印model为nil,是不是很奇怪,这是我没注意的一个坑,最后打断点才发现是应为类型的问题。
返回的都是long整形,但是声明的属性是string类型,所以我们要家这样的代码
+ (NSValueTransformer*)JSONTransformerForKey:(NSString *)key{
return [MTLValueTransformer transformerUsingReversibleBlock:^id(id value, BOOL *success, NSError *__autoreleasing *error) {
if (![value isKindOfClass:[NSString class]]) {
return [NSString stringWithFormat:@"%@",value];
}else
return value;
}];
}
将整形转换为string,这就ok啦
代码分析
- (id)initWithModelClass:(Class)modelClass {
NSParameterAssert(modelClass != nil);
NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);
self = [super init];
if (self == nil) return nil;
_modelClass = modelClass;
// 映射的json keypath
_JSONKeyPathsByPropertyKey = [modelClass JSONKeyPathsByPropertyKey];
// 属性key
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;
}
// keyPath 的值
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;
}
}
// 保存transformer 对象
_valueTransformersByPropertyKey = [self.class valueTransformersForModelClass:modelClass];
_JSONAdaptersByModelClass = [NSMapTable strongToStrongObjectsMapTable];
return self;
}
MTLValueTransformer.h
//只要是success = NO 转换就会失败
- (id)transformedValue:(id)value success:(BOOL *)outerSuccess error:(NSError **)outerError {
NSError *error = nil;
BOOL success = YES;
id transformedValue = self.forwardBlock(value, &success, &error);
if (outerSuccess != NULL) *outerSuccess = success;
if (outerError != NULL) *outerError = error;
return transformedValue;
}
下面的方法是获取相关属性的 transformer,,根局transformer才能知道我们要怎样转换对应的属性。
第一个for循环线遍历key+JSONTransformer 的方法,如果相应的model类中实现了就调用,获取transformer,例如上面的sysJSONTransformer,codJSONTransformer.如果实现了JSONTransformerForKey:方法,继续调用该方法,那这两个方法是否会冲突?哈哈,自己试验一下吧。
MTLJSONAdapter.m
+ (NSDictionary *)valueTransformersForModelClass:(Class)modelClass {
NSParameterAssert(modelClass != nil);
NSParameterAssert([modelClass conformsToProtocol:@protocol(MTLJSONSerializing)]);
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (NSString *key in [modelClass propertyKeys]) {
SEL selector = MTLSelectorWithKeyPattern(key, "JSONTransformer");
if ([modelClass respondsToSelector:selector]) {
IMP imp = [modelClass methodForSelector:selector];
NSValueTransformer * (*function)(id, SEL) = (__typeof__(function))imp;
NSValueTransformer *transformer = function(modelClass, selector);
if (transformer != nil) result[key] = transformer;
continue;
}
// 先掉用
if ([modelClass respondsToSelector:@selector(JSONTransformerForKey:)]) {
NSValueTransformer *transformer = [modelClass JSONTransformerForKey:key];
if (transformer != nil) result[key] = transformer;
continue;
}
objc_property_t property = class_getProperty(modelClass, key.UTF8String);
if (property == NULL) continue;
mtl_propertyAttributes *attributes = mtl_copyPropertyAttributes(property);
@onExit {
free(attributes);
};
NSValueTransformer *transformer = nil;
if (*(attributes->type) == *(@encode(id))) {
Class propertyClass = attributes->objectClass;
if (propertyClass != nil) {
transformer = [self transformerForModelPropertiesOfClass:propertyClass];
}
// For user-defined MTLModel, try parse it with dictionaryTransformer.
if (nil == transformer && [propertyClass conformsToProtocol:@protocol(MTLJSONSerializing)]) {
transformer = [self dictionaryTransformerWithModelClass:propertyClass];
}
if (transformer == nil) transformer = [NSValueTransformer mtl_validatingTransformerForClass:propertyClass ?: NSObject.class];
} else {
transformer = [self transformerForModelPropertiesOfObjCType:attributes->type] ?: [NSValueTransformer mtl_validatingTransformerForClass:NSValue.class];
}
if (transformer != nil) result[key] = transformer;
}
return result;
}