Mantle使用说明

图片发自简书App

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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,654评论 18 139
  • 概述 ​ iOS源码解析—YYModel(YYClassInfo)分析了如何根据OC的Class对象构建...
    egoCogito_panf阅读 11,577评论 4 32
  • 导语:YYModel库是优秀的模型转换库,可自动处理模型转换(从JSON到Model 和 Model到JSON)的...
    南华coder阅读 5,441评论 0 11
  • 结合数据层返回的 JSON 数据,Mantle 在基于 MVC 模式的应用的 Model 层中发挥了重要作用,包括...
    水止云起阅读 1,259评论 0 0
  • 理解了如何做才算投入全部,对于自己的规划多了些想法。如果以成长、注意力、耐心为出发点去思考自己每天要做的事情...
    雅倩1120阅读 133评论 0 1