iOS开发

Mantle使用说明

2016-05-03  本文已影响967人  楼上那位
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;
}
上一篇下一篇

猜你喜欢

热点阅读