无标题文章

Question

通常我们用Objective-C写的模型层遇到了什么问题?
在开发过程中,我们常常会从服务端获取数据,数据通常是JSON格式。 比较常见的做法是把JSON数据转为Model对象,这样我们可以从Model对象的属性读取数据。
如:
1、我们每次都要通过-initWithDictionarty:(NSDictionary *)dict解析json数据,把值一个一个赋值给MODEL对象,经常还会出现[[obj objectForKey:@“KEY”] objectAtIndex:index] objectForKey:@“”]这种层次比较深的遍历,层次关系也要小心处理,比较容易出错。
或者即使多了一层封装:

    _userAuthCode = authCode ? authCode : @"";
    _userAuthToken = token ? token : @"";
    _userUid = getStringInDict(jsonObject, @"uid");
    _pauth = getStringInDict(jsonObject, @"pauth");
    NSDictionary *info = getDictionaryInDict(jsonObject, @"info");
    _userUsername = getStringInDict(info, @"username");
    _userSex = getStringInDict(info, @"sex");

但是一样还要繁琐处理。
2、如果遇到服务端返回的类型不是确定的,更是蛋疼;有时候说是一个NSString,但是却给了个NSNumber,还有NSDate类型,给了NString类型,我们必须增加一些本来可以没有的判空。看到这么多if else就烦躁了。。

self.startKey = getStringInDict(result,@"startKey");
if (self.startKey == nil) {
  if (getIntInDict(result, @"startKey") == 0) {
   self.startKey = @"";
  }else{
   self.startKey = [NSString stringWithFormat:@"%d",getIntInDict(result,@"startKey")];
  }
}

3、还有就是接口出了问题,返回了NSNull,假设没有做好处理,还有可以就Crash.
4、还有Model—>Json
5、序列化归档问题

What:

mantleTitle
Mantle是一个用于简化Cocoa或Cocoa Touch程序中model层的第三方库。通常我们的应该中都会定义大量的model来表示各种数据结构,而这些model的初始化和编码解码都需要写大量的代码。

Mantle的优点在于能够大大地简化这些代码。是iOS和Mac平台下基于Objective-C编写的一个简单高效的模型层框架。
  Mantle源码中最主要的内容包括:

  • MTLModel类:通常是作为我们的Model的基类,该类提供了一些默认的行为来处理对象的初始化和归档操作,同时可以获取到对象所有属性的键值集合。
  • MTLJSONAdapter类:用于在MTLModel对象和JSON字典之间进行相互转换,相当于是一个适配器。
  • MTLJSONSerializing协议:需要与JSON字典进行相互转换的MTLModel的子类都需要实现该协议,以方便MTLJSONApadter对象进行转换。

##Use:
首先,看看Mantle的文件结构,文件还是蛮多的,不过常用的就那几个。MTDModel、MTLJSONAdapter

mantleTitle

TDModel

要使用的Model都必须继承MTDModel

初始化

MTLModel默认的初始化方法-init并没有做什么事情,只是调用了下[super init]。而同时,它提供了一个另一个初始化方法:

- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;

其中参数dictionaryValue是一个字典,它包含了用于初始化对象的key-value对。我们来看下它的具体实现:

- (instancetype)initWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {
    ...
    for (NSString *key in dictionary) {
        // 1. 将value标记为__autoreleasing,这是因为在MTLValidateAndSetValue函数中,
        //    可以会返回一个新的对象存在在该变量中
        __autoreleasing id value = [dictionary objectForKey:key];
        // 2. value如果为NSNull.null,会在使用前将其转换为nil
        if ([value isEqual:NSNull.null]) value = nil;
        // 3. MTLValidateAndSetValue函数利用KVC机制来验证value的值对于key是否有效,
        //    如果无效,则使用使用默认值来设置key的值。
        //    这里同样使用了对象的KVC特性来将value值赋值给model对应于key的属性。
        //    有关MTLValidateAndSetValue的实现可参考源码,在此不做详细说明。
        BOOL success = MTLValidateAndSetValue(self, key, value, YES, error);
        if (!success) return nil;
    }
    ...
}

子类可以重写该方法,可以在设置完对象的属性后做进一步的处理或初始化工作,记住:应该通过super来调用父类的实现**。

游戏盒的例子

h:
#import <Mantle/Mantle.h>

@interface MTDCommonGameListModel : MTLModel<MTLJSONSerializing>
@property (nonatomic,strong) NSString*  gameId;
@property (nonatomic) int               star;
@property (nonatomic) BOOL              hasVideo;
@property (nonatomic) MTDGameDownloadUrlType downloadType;
@property (nonatomic,strong) NSDate*    date;
@property (nonatomic,strong) NSString*  imageUrl;
@property (nonatomic,strong) SubModel*  subModel;
…
m:
#import "MTDCommonGameListModel.h"

@implementation MTDCommonGameListModel

+ (NSDictionary *)JSONKeyPathsByPropertyKey{
    return @{
         @"gameId":@"id",
         @"star":@"star",
         @"hasVideo":@"hasVideo",
         @"downloadType":@"downloadType",
     @"date":@"date”,
         @"imageUrl":@"img",
         @"subModel":@"subModel"
    };
    
}
//服务端返回的类型兼容 gameId可支持服务端返回 字符串或数值类型
+ (NSValueTransformer *)gameIdJSONTransformer {
    return [MTLValueTransformer transformerUsingForwardBlock:^id(id value, BOOL *success, NSError *__autoreleasing *error) {
        return [value stringValue];
    }];
}
//枚举类型映射
+ (NSValueTransformer *)stateJSONTransformer {
    return [NSValueTransformer mtl_valueMappingTransformerWithDictionary:@{                                                @"Others": @(MTDGameDownloadUrlTypeOthers),                                                                      @"Itunes": @(MTDGameDownloadUrlTypeItunes),                                                                  @"ShortLink": @(MTDGameDownloadUrlTypeShortLink)
 }];
}
//date
+ (NSValueTransformer *)dateJSONTransformer { 
      return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSNumber *dateNum) {
               return [NSDate dateWithTimeIntervalSince1970:dateNum.floatValue];  
 } 
 reverseBlock:^(NSDate *date) {
  return [NSString stringWithFormat:@"%f",[date timeIntervalSince1970]]; 
 }];
}
//Model里面的Model实现
+ (NSValueTransformer *)subModelJSONTransformer {
    return [MTLJSONAdapter dictionaryTransformerWithModelClass:SubModel.class];
}

- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError *__autoreleasing *)error{
    self = [super initWithDictionary:dictionaryValue error:error];
    if (self == nil) {
        return nil;
    }
    return self;
    
}

如果需要转换的属性可以增加对应的方法,通过运行时处理的,方法命名规则是 [属性JSONTransformer],对这个属性进行赋值时会调用这个方法先进行转换。
当JSON数据里有NSNull的类型时,我们不用做任何处理,会自动将该属性置为nil;

Model里面的Model实现

  • (NSValueTransformer *)subModelJSONTransformer {
    return [MTLJSONAdapter dictionaryTransformerWithModelClass:subModel.class];
    }

reverseBlock model —>JSON

reverseBlock是干什么的呢? 当要把Model转换回JSON数据时,如果设置了返回值,那么会将NSDate转回NSNumber返回JSON数据。
我们可以调用 MTLJSONAdapter的

+ (NSDictionary *)JSONDictionaryFromModel:<#(id<MTLJSONSerializing>)#> error:<#(NSError *__autoreleasing *)#>;

方法将一个Model实例转回JSON数据。

合并对象

- (void)mergeValueForKey:(NSString *)key fromModel:(MTLModel *)model;

该方法将当前对象指定的key属性的值与model参数对应的属性值按照指定的规则来进行合并,这种规则由我们自定义的-mergeFromModel:方法来确定。如果我们的子类中实现了-mergeFromModel:方法,则会调用它;如果没有找到,且model不为nil,则会用model的属性的值来替代当前对象的属性的值。具体实现如下:

- (void)mergeValueForKey:(NSString *)key fromModel:(MTLModel *)model {
    NSParameterAssert(key != nil);
    // 1. 根据传入的key拼接"mergeFromModel:"字符串,并从该字符串中获取到对应的selector
    //    如果当前对象没有实现-mergeFromModel:方法,且model不为nil,则用model的属性值
    //    替代当前对象的属性值
    //
    //    MTLSelectorWithCapitalizedKeyPattern函数以C语言的方式来拼接方法字符串,具体实现请
    //    参数源码,在此不详细说明
    SEL selector = MTLSelectorWithCapitalizedKeyPattern("merge", key, "FromModel:");
    if (![self respondsToSelector:selector]) {
        if (model != nil) {
            [self setValue:[model valueForKey:key] forKey:key];
        }
        return;
    }
    // 2. 通过NSInvocation方式来调用对应的-mergeFromModel:方法。
     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:  [self methodSignatureForSelector:selector]];
     invocation.target = self;
     invocation.selector = selector;
     [invocation setArgument:&model atIndex:2];
     [invocation invoke];
 }

此外,MTLModel还提供了另一个方法来合并两个对象所有的属性值,即:

 - (void)mergeValuesForKeysFromModel:(MTLModel *)model;

需要注意的是model必须是当前对象所属类或其子类。

<NSCoding>, <NSCopying>, -isEqual:和-hash

在你的子类里面生命属性,MTLModel可以提供这些方法的默认实现

Mantle配合归档

MTLModel默认实现了 NSCoding协议,可以利用NSKeyedArchiver方便的对对象进行归档和解档。

Mantle配合Core Data

….这个后续有时间再补充,目前还没整理

##****参考:
https://github.com/mantle/mantle
https://segmentfault.com/a/1190000003882034
http://www.jianshu.com/p/937266eb6635

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 【2017年最新】☞ iOS面试题及答案 设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经...
    紫色冰雨阅读 594评论 0 1
  • MVC 具有什么样的优势,各个模块之间怎么通信,比如点击 Button 后 怎么通知 Model?[iOS] MV...
    Lost_693d阅读 146评论 0 1
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,204评论 0 4
  • 距离林宥嘉微博求婚丁文琪过去好几天了。直到现在才谈这个话题,似乎有点晚,但其实有一些情绪在知道这个热门的时候就已经...
    薰微之恋阅读 483评论 0 3