美文网首页iOS开发
Mantle使用说明

Mantle使用说明

作者: 楼上那位 | 来源:发表于2016-05-03 22:55 被阅读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;
    }
    

    相关文章

      网友评论

      本文标题:Mantle使用说明

      本文链接:https://www.haomeiwen.com/subject/vulrrttx.html